Python-Excel-指南-全-
Python Excel 指南(全)
原文:
zh.annas-archive.org/md5/4cbcfc6fe67ce954227c7b15eef2b4c0
译者:飞龙
前言
Microsoft 正在运行一个 Excel 的反馈论坛UserVoice,每个人都可以提交新的想法供其他人投票。最受欢迎的功能请求是“将 Python 作为 Excel 脚本语言”,它的票数大约是第二名请求的两倍。尽管自 2015 年提出这个想法以来并没有真正发生什么,但在 2020 年底,当 Python 的创始人 Guido van Rossum 发推文说他的“退休很无聊”,并且他将加入 Microsoft 时,Excel 用户们又充满了新的希望。他的举动是否对 Excel 和 Python 的整合有任何影响,我不知道。但我确实知道,这种组合的魅力何在,以及你如何可以今天就开始使用 Excel 和 Python。这本书就是关于这个的简要概述。
Python 背后的主要推动力是我们生活在一个数据世界中的事实。如今,巨大的数据集对每个人都是可用的,而且无所不包。通常,这些数据集非常庞大,无法再适应电子表格。几年前,这可能被称为大数据,但如今,几百万行的数据集真的不算什么了。Excel 已经发展到可以应对这一趋势:它引入了 Power Query 来加载和清理那些无法适应电子表格的数据集,并且引入了 Power Pivot,一个用于对这些数据集进行数据分析并呈现结果的附加组件。Power Query 基于 Power Query M 公式语言(M),而 Power Pivot 则使用数据分析表达式(DAX)定义公式。如果你也想在 Excel 文件中自动化一些事情,那么你会使用 Excel 内置的自动化语言 Visual Basic for Applications(VBA)。也就是说,对于相对简单的任务,你最终可能会使用 VBA、M 和 DAX。其中一个问题是,所有这些语言只能在 Microsoft 世界中为你提供服务,尤其是在 Excel 和 Power BI 中(我将在第一章中简要介绍 Power BI)。
Python,另一方面,是一种通用的编程语言,已经成为分析师和数据科学家中最受欢迎的选择之一。如果你在 Excel 中使用 Python,你可以使用一种擅长于各个方面的编程语言,无论是自动化 Excel、访问和准备数据集,还是执行数据分析和可视化任务。最重要的是,你可以在 Excel 之外重复使用你的 Python 技能:如果你需要扩展计算能力,你可以轻松地将你的定量模型、仿真或机器学习应用移至云端,那里几乎没有限制的计算资源在等待着你。
为什么我写了这本书
通过我在 xlwings 上的工作,这是我们将在本书的第四部分中见到的 Excel 自动化包,我与许多使用 Python 处理 Excel 的用户保持密切联系 —— 无论是通过 GitHub 上的问题跟踪器,还是在 StackOverflow 上的问题,或者像会议和聚会这样的实际活动中。
我经常被要求推荐入门 Python 的资源。虽然 Python 的介绍确实不少,但它们往往要么太泛泛(没有关于数据分析的内容),要么太专业(完全是科学的介绍)。然而,Excel 用户往往处于中间地带:他们确实处理数据,但完全科学的介绍可能太技术化。他们也经常有特定的需求和问题,在现有的材料中找不到答案。其中一些问题是:
-
我需要哪个 Python-Excel 包来完成哪些任务?
-
-
如何将我的 Power Query 数据库连接迁移到 Python?
-
-
Python 中的 AutoFilter 或数据透视表等于 Excel 中的什么?
我写这本书是为了帮助你从零开始学习 Python,以便能够自动化你与 Excel 相关的任务,并利用 Python 的数据分析和科学计算工具在 Excel 中进行操作,而不需要任何绕道。
本书的受众
如果你是一个高级的 Excel 用户,希望用现代编程语言突破 Excel 的限制,那么这本书适合你。最典型的情况是,你每个月花数小时下载、清理和复制/粘贴大量数据到关键的电子表格中。
你应该具备基本的编程理解:如果你已经写过函数或者 for 循环(无论是哪种编程语言),并且知道什么是整数或字符串,那么会有所帮助。即使你习惯编写复杂的单元格公式或有调整过的记录的 VBA 宏经验,你也可以掌握本书。不过,并不要求你有任何 Python 特定的经验,因为我们将会介绍我们将使用的所有工具,包括 Python 自身的介绍。
如果你是一个经验丰富的 VBA 开发者,你会发现 Python 和 VBA 之间的常见比较,这将帮助你避开常见的陷阱,快速上手。
如果你是一名 Python 开发者,并且需要了解 Python 处理 Excel 应用程序和 Excel 文件的不同方式,以便根据业务用户的需求选择正确的包,那么这本书也会对你有所帮助。
本书的组织结构
在本书中,我将展示 Python 处理 Excel 的所有方面,分为四个部分:
第一部分:Python 简介
本部分首先探讨了为什么 Python 是 Excel 的理想伴侣的原因,然后介绍了本书中将要使用的工具:Anaconda Python 发行版、Visual Studio Code 和 Jupyter 笔记本。本部分还将教会你足够的 Python 知识,以便能够掌握本书的其余内容。
第二部分:pandas 介绍
pandas 是 Python 的数据分析首选库。我们将学习如何使用 Jupyter 笔记本和 pandas 组合来替换 Excel 工作簿。通常,pandas 代码既更容易维护又更高效,而且您可以处理不适合电子表格的数据集。与 Excel 不同,pandas 允许您在任何地方运行您的代码,包括云中。
第三部分:不使用 Excel 读写 Excel 文件
本部分介绍了使用以下 Python 包之一操纵 Excel 文件的方法:pandas、OpenPyXL、XlsxWriter、pyxlsb、xlrd 和 xlwt。这些包能够直接在磁盘上读取和写入 Excel 工作簿,并因此取代了 Excel 应用程序:由于不需要安装 Excel,因此它们适用于 Python 支持的任何平台,包括 Windows、macOS 和 Linux。阅读器包的典型用例是从外部公司或系统每天早晨收到的 Excel 文件中读取数据,并将其内容存储在数据库中。写入器包的典型用例是为您在几乎每个应用程序中都可以找到的着名的“导出到 Excel”按钮提供功能。
第四部分:使用 xlwings 编程 Excel 应用程序
在本部分中,我们将看到如何使用 xlwings 包将 Python 与 Excel 应用程序自动化,而不是在磁盘上读写 Excel 文件。因此,本部分需要您在本地安装 Excel。我们将学习如何打开 Excel 工作簿并在我们眼前操纵它们。除了通过 Excel 读写文件之外,我们还将构建交互式 Excel 工具:这些工具允许我们点击按钮,使 Python 执行您以前可能使用 VBA 宏执行的一些计算机密集型计算。我们还将学习如何在 Python 中编写用户定义的函数 1(UDFs),而不是 VBA 中。
非常重要的是要理解阅读和写入 Excel 文件(第三部分)与编程 Excel 应用程序(第四部分)之间的根本区别,如图 P-1 所示。
图 P-1. 读写 Excel 文件(第三部分)与编程 Excel(第四部分)
由于第三部分不需要安装 Excel,因此在 Python 支持的所有平台上都可以工作,主要是 Windows、macOS 和 Linux。然而,第四部分只能在 Microsoft Excel 支持的平台上工作,即 Windows 和 macOS,因为代码依赖于本地安装的 Microsoft Excel。
Python 和 Excel 版本
本书基于 Python 3.8,这是 Anaconda Python 发行版的最新版本附带的 Python 版本。如果你想使用更新的 Python 版本,请按照书籍首页上的说明操作,但要确保不使用旧版本。如果 Python 3.9 有变化,我会偶尔进行评论。
本书还期望你使用现代版本的 Excel,至少是 Windows 上的 Excel 2007 和 macOS 上的 Excel 2016。配有 Microsoft 365 订阅的本地安装版本的 Excel 也可以完美地工作——事实上,我甚至推荐使用它,因为它具有其他 Excel 版本中找不到的最新功能。这也是我撰写本书时使用的版本,所以如果你使用其他版本的 Excel,可能会偶尔看到菜单项名称或位置有轻微差异。
本书使用的约定
本书使用以下排版约定:
斜体
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
常规宽度
用于程序清单,以及段落内引用的程序元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。
常规宽度粗体
显示用户应该按照字面意思输入的命令或其他文本。
常规斜体
显示应由用户提供的值或由上下文确定的值替换的文本。
提示
此元素表示提示或建议。
注意
此元素表示一般说明。
警告
此元素表示警告或注意事项。
使用代码示例
我在网页上提供了额外的信息,帮助你理解这本书。确保查看,特别是如果你遇到问题时。
补充材料(代码示例、练习等)可以从github.com/fzumstein/python-for-excel
下载。要下载这个配套的仓库,请点击绿色的“Code”按钮,然后选择下载 ZIP。下载后,在 Windows 上右键单击文件并选择“解压缩全部”以解压缩文件到文件夹中。在 macOS 上,只需双击文件即可解压缩。如果你知道如何使用 Git,也可以使用 Git 将仓库克隆到本地硬盘上。你可以将文件夹放在任何位置,但在本书中我偶尔会按照以下方式提及它:
C:\Users\``username``\python-for-excel
在 Windows 上简单下载并解压 ZIP 文件后,您会得到一个类似于以下结构的文件夹(请注意重复的文件夹名称):
C:\...\Downloads\python-for-excel-1st-edition\python-for-excel-1st-edition
将此文件夹的内容复制到您在 C:\Users<username>\python-for-excel 下创建的文件夹中,可能会使您更容易跟进。对于 macOS,即将文件复制到 /Users/
如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至 bookquestions@oreilly.com。
本书旨在帮助您完成工作任务。一般而言,如果本书提供了示例代码,您可以在自己的程序和文档中使用它。除非您要复制大部分代码,否则无需事先联系我们以获取许可。例如,编写一个使用本书多个代码片段的程序并不需要许可。出售或分发 O’Reilly 图书的示例代码则需要许可。通过引用本书并引用示例代码回答问题无需许可。将本书大量示例代码整合到产品文档中则需要许可。
我们感谢您的使用,但通常不需要署名。署名通常包括标题、作者、出版商和 ISBN。例如:“Python for Excel by Felix Zumstein (O’Reilly). Copyright 2021 Zoomer Analytics LLC, 978-1-492-08100-5.”
如果您认为您使用的代码示例超出了公平使用范围或上述许可的限制,请随时联系我们,邮箱为 permissions@oreilly.com。
O’Reilly 在线学习
注意
40 多年来,O’Reilly Media 提供技术和商业培训、知识和见解,帮助公司取得成功。
我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专业知识。O’Reilly 的在线学习平台为您提供按需访问实时培训课程、深度学习路径、交互式编码环境以及来自 O’Reilly 和其他 200 多家出版商的大量文本和视频。欲了解更多信息,请访问 oreilly.com
。
如何联系我们
请就本书的评论和问题联系出版商:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-998-9938(在美国或加拿大)
-
707-829-0515(国际或当地)
-
707-829-0104(传真)
我们为本书设有一个网页,其中列出勘误、示例和任何额外信息。您可以访问 oreil.ly/py4excel
。
发送电子邮件至 bookquestions@oreilly.com 以评论或提出关于本书的技术问题。
欲了解更多关于我们的书籍、课程、会议和新闻的信息,请访问我们的网站:www.oreilly.com
。
在 Facebook 上找到我们:facebook.com/oreilly
。
在 Twitter 上关注我们:twitter.com/oreillymedia
。
在 YouTube 上观看我们:www.youtube.com/oreillymedia
。
致谢
作为初次撰写书籍的作者,我非常感激沿途得到的许多人的帮助,他们让这段旅程对我来说变得更加轻松!
在 O'Reilly,我要感谢我的编辑 Melissa Potter,在保持我积极进度和帮助我将这本书变得可读方面做得很好。我还要感谢 Michelle Smith,她与我一起工作在初步书籍提案上,以及 Daniel Elfanbaum,他从不厌倦回答我的技术问题。
非常感谢所有投入大量时间阅读我初稿的同事、朋友和客户。他们的反馈对于使书籍更易理解至关重要,一些案例研究受到了他们与我分享的真实 Excel 问题的启发。我的感谢送给 Adam Rodriguez、Mano Beeslar、Simon Schiegg、Rui Da Costa、Jürg Nager 和 Christophe de Montrichard。
我还从 O'Reilly 在线学习平台的早期发布版本的读者那里得到了有用的反馈。感谢 Felipe Maion、Ray Doue、Kolyu Minevski、Scott Drummond、Volker Roth 和 David Ruggles!
我非常幸运,这本书得到了高素质的技术审阅人员的审查,我真的很感谢他们在很大时间压力下所付出的辛勤工作。感谢你们所有的帮助,Jordan Goldmeier、George Mount、Andreas Clenow、Werner Brönnimann 和 Eric Moreira!
特别感谢 Björn Stiel,他不仅是技术审阅人员,还是我在这本书中学到许多知识的导师。这些年来,很高兴能和你一起工作!
最后但并非最不重要的,我要感谢 Eric Reynolds,在 2016 年将他的 ExcelPython 项目合并到 xlwings 代码库中。他还从头重新设计了整个包,使我早期可怕的 API 成为过去。非常感谢你!
1 Microsoft 已开始使用术语自定义函数而不是 UDF。在本书中,我将继续称它们为 UDF。
第一部分:Python 入门
第一章:为什么选择 Python 来操作 Excel?
通常,Excel 用户在遇到限制时才开始质疑他们的电子表格工具。一个经典的例子是当 Excel 工作簿包含了大量数据和公式时变得缓慢或在最坏的情况下崩溃。然而,在事情变得糟糕之前,质疑你的设置是有意义的:如果你在关键任务的工作簿上工作,其中的错误可能会导致财务或声誉损失,或者如果你每天花数小时手动更新 Excel 工作簿,那么你应该学会如何用编程语言自动化你的流程。自动化消除了人为错误的风险,并允许你将时间花在比复制/粘贴数据到 Excel 电子表格更有生产力的任务上。
在本章中,我将给出一些理由,解释为什么 Python 与 Excel 结合是一个极好的选择,以及与 Excel 内置的自动化语言 VBA 相比的优势。在介绍 Excel 作为一种编程语言并了解其特殊性之后,我将指出使 Python 与 VBA 相比如此强大的具体特性。不过,首先让我们简单地了解一下我们的两位主角的起源!
就计算机技术而言,Excel 和 Python 都已经存在了很长时间:Excel 首次于 1985 年由微软推出——这可能会让很多人感到惊讶——它只能在苹果麦金塔上使用。直到 1987 年,微软 Windows 才推出了 Excel 2.0 的第一个版本。然而微软并不是电子表格市场上的第一家公司:VisiCorp 在 1979 年推出了 VisiCalc,随后 Lotus Software 在 1983 年推出了 Lotus 1-2-3。而微软也不是首次推出 Excel:三年前,他们推出了 Multiplan,一款可在 MS-DOS 和其他一些操作系统上使用的电子表格程序,但无法在 Windows 上运行。
Python 诞生于 1991 年,仅比 Excel 晚六年。虽然 Excel 很早就流行起来了,但 Python 直到后来才在某些领域得到了采用,如 Web 开发或系统管理。2005 年,Python 开始成为科学计算的一个严肃的替代品,当时第一个数组计算和线性代数包 NumPy 首次发布。NumPy 合并了两个前身包,因此将所有关于科学计算的开发工作整合到了一个项目中。如今,它构成了无数科学包的基础,包括 2008 年推出的 pandas,它在数据科学和金融领域的广泛采用始于 2010 年之后。多亏了 pandas,Python 与 R 一起成为了数据分析、统计和机器学习等数据科学任务中最常用的语言之一。
Python 和 Excel 都是很久以前发明的事实并不是它们共同的唯一之处:Excel 和 Python 还都是一种编程语言。虽然您可能对 Python 的这一点不感到惊讶,但对于 Excel,您可能需要一些解释,我将在接下来为您解释。
Excel 是一种编程语言。
本节首先介绍 Excel 作为一种编程语言,这将帮助您理解为什么电子表格问题经常出现在新闻中。然后,我们将看看一些在软件开发社区中出现的最佳实践,这些实践可以避免许多典型的 Excel 错误。最后,我们将简要介绍 Power Query 和 Power Pivot,这两种现代 Excel 工具涵盖了我们将使用 pandas 替代的功能类型。
如果您不只是用 Excel 列出您的杂货清单,您肯定正在使用类似=SUM(A1:A4)
这样的函数来对一系列单元格求和。如果您稍加思考如何工作,您会注意到一个单元格的值通常取决于一个或多个其他单元格,而这些可能又使用依赖于一个或多个其他单元格的函数,如此反复。进行这种嵌套函数调用与其他编程语言的工作方式没有什么不同,只是您在单元格中写代码而不是文本文件中。如果这还没有完全说服您:在 2020 年底,微软宣布引入 Lambda 函数,允许您在 Excel 的自有公式语言中编写可重用的函数,即不必依赖于像 VBA 这样的其他语言。根据 Brian Jones,Excel 产品负责人的说法,这是最终使 Excel 成为“真正的”编程语言的缺失部分。这也意味着 Excel 用户实际上应该被称为 Excel 程序员!
然而,Excel 程序员有一个特殊的地方:大多数都是没有计算机科学正规教育的业务用户或领域专家。他们是交易员、会计师或工程师,仅举几个例子。他们的电子表格工具旨在解决业务问题,通常忽略软件开发中的最佳实践。因此,这些电子表格工具经常在同一张表上混合输入、计算和输出,可能需要执行非明显的步骤才能正常工作,并且关键更改是没有安全网的。换句话说,这些电子表格工具缺乏坚实的应用架构,并且通常没有文档化和未经测试。有时,这些问题可能会产生灾难性后果:如果您在下单前忘记重新计算交易工作簿,可能会买入或卖出错误数量的股票,从而导致您损失资金。如果不只是您自己的资金在交易,我们可以在新闻中看到相关报道,接下来我们将看到。
Excel 在新闻中。
Excel 经常出现在新闻中,而在撰写本文期间,又有两则新闻成为头条。第一则是关于 HUGO 基因命名委员会,他们重新命名了一些人类基因,以免被 Excel 误读为日期。例如,为了防止基因MARCH1
被转换成1-Mar
,它被改名为MARCHF1
。2 第二则新闻是,Excel 因为英格兰 16000 份 COVID-19 检测结果的延迟报告而受到指责。问题是检测结果写入的是较老的 Excel 文件格式(.xls),该格式限制了大约 65000 行数据。这意味着超出该限制的更大数据集将被简单截断。3 虽然这两则故事显示了 Excel 在当今世界的持续重要性和主导地位,但可能没有其他“Excel 事件”比伦敦鲸更为著名。
伦敦鲸是一个交易员的绰号,他的交易失误迫使摩根大通在 2012 年宣布惊人的 60 亿美元损失。灾难的根源是一个基于 Excel 的风险价值模型,该模型严重低估了他们其中一个投资组合失钱的真实风险。JPMorgan Chase & Co.管理任务组关于 2012 年 CIO 损失的报告 4(2013 年)提到,“该模型通过一系列 Excel 电子表格运行,必须通过复制和粘贴数据从一个电子表格到另一个电子表格的过程手动完成。”除了这些操作问题,他们还有一个逻辑错误:在一个计算中,他们除以一个总和而不是一个平均值。
如果您想了解更多这类故事,请查看由欧洲电子表格风险兴趣小组(EuSpRIG)维护的恐怖故事网页。
为了防止您的公司出现类似的新闻报道,让我们接下来看看几个最佳实践,可以大大提高您使用 Excel 的工作安全性。
编程最佳实践
本节将介绍您需要了解的最重要的编程最佳实践,包括关注点分离、DRY 原则、测试和版本控制。正如我们将看到的那样,当您开始将 Python 与 Excel 一起使用时,遵循这些原则将会更加容易。
关注点分离
编程中最重要的设计原则之一是关注点分离,有时也称为模块化。这意味着相关功能集应由程序的独立部分处理,以便可以轻松替换而不影响应用程序的其余部分。在最高层面上,一个应用程序通常分为以下几个层次:5
-
展示层
-
-
业务层
-
-
数据层
要解释这些层次,请考虑一个简单的货币转换器,例如在图 1-1 中显示的一个。您可以在伴侣存储库的 xl 文件夹中找到 currency_converter.xlsx Excel 文件。
这是应用程序的工作原理:在单元格 A4 和 B4 中键入金额和货币,Excel 将把它转换为美元显示在 D4 单元格中。许多电子表格应用程序都遵循这样的设计,并且每天被企业使用。让我逐层分解这个应用程序:
展示层
这是您看到并与之交互的用户界面:单元格 A4、B4 和 D4 的值及其标签共同构建了货币转换器的展示层。
业务层
这一层处理应用程序特定的逻辑:D4 单元格定义了如何将金额转换为美元。公式
=A4 * VLOOKUP(B4, F4:G11, 2, FALSE)
表示金额乘以汇率。
数据层
正如名称所示,这一层负责访问数据:D4 单元格中的
VLOOKUP
部分正在执行此任务。
数据层从汇率表中访问数据,该表从单元格 F3 开始,并充当这个小应用程序的数据库。如果您仔细观察,您可能会注意到单元格 D4 在所有三个层中都出现了:这个简单的应用程序在一个单元格中混合了展示、业务和数据层。
图 1-1. currency_converter.xlsx
对于这个简单的货币转换器来说,这并不一定是问题,但通常,一个开始时是小型 Excel 文件很快就会变成一个更大的应用程序。如何改善这种情况?大多数专业的 Excel 开发资源建议您为每个层次使用单独的工作表,在 Excel 的术语中通常称为输入、计算和输出。通常,这与为每个层次定义特定的颜色代码相结合,例如,所有输入单元格的蓝色背景。在第十一章中,我们将基于这些层次构建一个真实的应用程序:Excel 将作为展示层,而业务和数据层将移至 Python 中,这样可以更轻松地正确结构化您的代码。
现在您知道了关注点分离的含义,让我们了解一下 DRY 原则是什么!
DRY 原则
Hunt 和 Thomas(Pearson Education)在《实用程序员》一书中推广了 DRY 原则:不要重复自己。没有重复的代码意味着代码行数更少,错误也更少,使代码更易于维护。如果您的业务逻辑存在于单元格公式中,那么在另一个工作簿中重复使用它是几乎不可能的,因为没有机制可以实现这一点。不幸的是,这意味着启动新的 Excel 项目的常见方法是从以前的项目或模板复制工作簿。
如果你写 VBA,最常见的可重用代码片段是函数。函数使你能够从多个宏中访问同一代码块,例如。如果你有多个经常使用的函数,你可能希望在工作簿之间共享它们。在工作簿之间共享 VBA 代码的标准工具是加载项,但是 VBA 加载项缺乏分发和更新的强大方式。尽管微软推出了一个 Excel 内部加载项商店来解决这个问题,但这仅适用于基于 JavaScript 的加载项,因此对于 VBA 程序员来说并不是一个选择。这意味着,仍然很普遍地使用复制/粘贴方法与 VBA:假设你需要在 Excel 中一个三次样条函数。三次样条函数是一种根据坐标系中几个给定点插值曲线的方式,通常被固定收益交易员用来根据少数已知到期日/利率组合推导所有到期期限的利率曲线。如果你在互联网上搜索“Cubic Spline Excel”,不久就会找到一个能做你想做的事情的 VBA 代码页面。问题在于,这些函数通常是由单个人编写的,可能出于良好意图,但没有正式的文档或测试。也许它们对大多数输入有效,但对一些不常见的边缘情况呢?如果你在交易数百万的固定收益投资组合,你肯定希望有一些你知道可以信赖的东西。至少,这就是当内部审计员发现代码来源时,你会听到的意见。
Python 通过使用包管理器使得代码分发变得容易,我们将在本章的最后一节看到。然而,在那之前,让我们继续讨论测试,这是坚实软件开发的基石之一。
测试
当你告诉一个 Excel 开发者测试他们的工作簿时,他们很可能会执行一些随机检查:点击一个按钮并查看宏是否仍然执行其预期功能,或者更改一些输入并检查输出是否合理。然而,这是一种风险较高的策略:Excel 很容易引入难以察觉的错误。例如,你可以用硬编码值覆盖一个公式,或者忘记调整隐藏列中的一个公式。
当你告诉一个专业的软件开发者测试他们的代码时,他们会编写单元测试。顾名思义,这是测试程序单个组件的机制。例如,单元测试确保程序的单个函数正常工作。大多数编程语言提供一种自动运行单元测试的方法。运行自动化测试将极大增加代码库的可靠性,并相当确保在编辑代码时不会破坏当前正常运行的任何功能。
如果你看看图 1-1 中的货币转换工具,你可以编写一个测试来检查当输入为 100 欧元和 1.05 作为欧元兑美元汇率时,单元格 D4 中的公式是否正确返回 USD 105。这有什么帮助呢?假设你意外删除了包含转换公式的单元格 D4,并且不得不重新编写它:与其用汇率乘以金额,你误用了除以汇率——毕竟,处理货币可能很令人困惑。当你运行上述测试时,你会得到一个测试失败,因为 100 欧元 / 1.05 不再像测试期望的那样得到 105 美元。通过这种方式,你可以在将电子表格交给用户之前检测并修复公式。
几乎所有传统的编程语言都提供一个或多个测试框架来轻松编写单元测试,但 Excel 不行。幸运的是,单元测试的概念足够简单,并且通过将 Excel 与 Python 连接,你可以使用 Python 强大的单元测试框架。虽然本书不涉及单元测试的更深入介绍,但我邀请你阅读我的博客文章,其中我通过实际例子为你详细讲解这个主题。
单元测试通常在你将代码提交到版本控制系统时自动运行。下一节将解释版本控制系统的概念及其与 Excel 文件难以配合的原因。
版本控制
专业程序员的另一个特征是他们使用版本控制或源代码控制系统。版本控制系统(VCS)会随时间跟踪源代码的变化,允许你查看谁、何时以及为何做了变更,并允许你随时恢复到旧版本。如今最流行的版本控制系统是Git,最初是为管理 Linux 源代码而创建的,此后已占领了编程世界——甚至微软也在 2017 年采用 Git 管理 Windows 源代码。相比之下,在 Excel 的世界中,迄今为止最流行的版本控制系统是以文件夹形式存档文件,就像这样:
currency_converter_v1.xlsx currency_converter_v2_2020_04_21.xlsx currency_converter_final_edits_Bob.xlsx currency_converter_final_final.xlsx
如果,与此样本不同,Excel 开发者在文件名中坚持某种约定,这本身并没有什么问题。但是,在本地保留文件的版本历史会让您失去源代码控制的重要方面,例如更轻松的协作、同行审查、签署流程和审计日志。如果您想使工作簿更安全和稳定,您不想错过这些功能。通常情况下,专业程序员会将 Git 与像 GitHub、GitLab、Bitbucket 或 Azure DevOps 这样的基于 Web 的平台结合使用。这些平台允许您使用所谓的拉取请求或合并请求。它们允许开发人员正式请求将其更改合并到主代码库中。拉取请求提供以下信息:
-
谁是更改的作者
-
-
更改是什么时候进行的
-
-
更改的目的如何在提交消息中描述
-
-
更改的详细信息如何通过 diff 视图显示,即高亮显示新代码的绿色和删除代码的红色
这允许同事或团队负责人审查更改并发现异常。通常情况下,额外的一双眼睛能够发现一个或两个问题,或者向程序员提供宝贵的反馈。有了所有这些优势,为什么 Excel 开发者更喜欢使用本地文件系统和他们自己的命名约定,而不是像 Git 这样的专业系统呢?
-
许多 Excel 用户根本不了解 Git 或者很早就放弃了,因为 Git 学习曲线相对较陡。
-
-
Git 允许多个用户并行在本地副本上工作在同一个文件上。当所有人提交他们的工作后,Git 通常可以自动合并所有更改,无需任何手动干预。但这对 Excel 文件不适用:如果它们在分开的副本上并行更改,Git 就无法将这些更改合并回一个单一的文件中。
-
-
即使你设法处理了之前的问题,Git 对于 Excel 文件的价值远不及对文本文件:Git 无法显示 Excel 文件之间的更改,这阻碍了适当的同行审查流程。
因为所有这些问题,我们公司推出了 xltrail,一个基于 Git 的版本控制系统,它知道如何处理 Excel 文件。它隐藏了 Git 的复杂性,使业务用户感觉舒适使用它,同时还允许您连接到外部 Git 系统,例如,如果您已经在 GitHub 中跟踪您的文件。xltrail 跟踪工作簿的不同组成部分,包括单元格公式、命名区域、Power Queries 和 VBA 代码,使您能够利用版本控制的经典优势,包括同行审查。
另一个使得使用 Excel 更容易进行版本控制的选项是将你的业务逻辑从 Excel 转移到 Python 文件中,这是我们将在第十章中完成的事情。由于 Python 文件易于使用 Git 进行跟踪,你将能够完全控制你电子表格工具的最重要部分。
尽管本节称为“编程最佳实践”,但主要是指出为什么在 Excel 中遵循它们比在像 Python 这样的传统编程语言中更难。在我们把注意力转向 Python 之前,我想简要介绍一下 Power Query 和 Power Pivot,这是微软试图使 Excel 现代化的尝试。
现代 Excel
Excel 的现代时代始于 Excel 2007,当时引入了功能区菜单和新的文件格式(例如 xlsx 替代 xls)。然而,Excel 社区将现代 Excel 定义为随 Excel 2010 添加的工具:尤其是 Power Query 和 Power Pivot。它们允许您连接到外部数据源并分析无法容纳在电子表格中的大型数据。由于它们的功能与我们将在第五章中使用 pandas 进行的工作重叠,我将在本节的第一部分简要介绍它们。第二部分介绍 Power BI,这可以被描述为一个独立的商业智能应用程序,结合了 Power Query 和 Power Pivot 的功能,并具有内置的 Python 支持!
Power Query 和 Power Pivot
在 Excel 2010 中,微软引入了一个名为 Power Query 的插件。Power Query 能够连接多种数据源,包括 Excel 工作簿、CSV 文件和 SQL 数据库。它还支持连接到像 Salesforce 这样的平台,甚至可以扩展到连接没有预先支持的系统。Power Query 的核心功能是处理那些无法容纳在电子表格中的大型数据集。加载数据后,您可以执行额外的步骤来清理和操作数据,使其以可用形式出现在 Excel 中。例如,您可以将一列拆分为两列,合并两个表,或者过滤和分组数据。自 Excel 2016 起,Power Query 不再是一个插件,而是可以直接通过“数据”选项卡上的“获取数据”按钮访问。然而,Power Query 在 macOS 上仅部分可用——不过,它正在积极开发中,因此应该在未来的 Excel 发布中得到充分支持。
Power Pivot 与 Power Query 配合得天衣无缝:在使用 Power Query 获取和清洗数据后,Power Pivot 是概念上的第二步。Power Pivot 帮助你直接在 Excel 中分析和展示数据。可以将其视为传统数据透视表,类似于 Power Query,可以处理大型数据集。Power Pivot 允许你定义具有关系和层次结构的正式数据模型,并且可以通过 DAX 公式语言添加计算列。Power Pivot 也是在 Excel 2010 中引入的,但仍然是一个附加组件,目前尚不支持 macOS。
如果你喜欢使用 Power Query 和 Power Pivot,并希望在其上构建仪表板,那么值得一试 Power BI — 让我们看看为什么!
Power BI
Power BI 是一个独立应用,于 2015 年发布。这是微软对 Tableau 或 Qlik 等商业智能工具的回应。Power BI Desktop 是免费的,如果你想尝试一下,请访问Power BI 官网并下载它 — 不过需要注意的是,Power BI Desktop 仅适用于 Windows。Power BI 通过可视化交互式仪表板来理解大型数据集。其核心功能依赖于与 Excel 相同的 Power Query 和 Power Pivot 功能。商业版计划允许你在线协作和共享仪表板,但与桌面版是分开的。在本书的背景下,Power BI 之所以令人兴奋的主要原因是,自 2018 年起支持 Python 脚本。Python 不仅可以用于查询部分,还可以利用 Python 的绘图库进行可视化部分。对我来说,在 Power BI 中使用 Python 有些笨拙,但重要的是微软已经认识到 Python 在数据分析中的重要性。因此,人们对 Python 有望正式进入 Excel 持久地寄予厚望。
那么,Python 有何优点,使其进入微软的 Power BI?下一节将给出一些答案!
Python for Excel
Excel 专注于存储、分析和可视化数据。由于 Python 在科学计算领域特别强大,因此与 Excel 结合非常自然。Python 也是极少数同时吸引专业程序员和每隔几周写几行代码的初学者的语言之一。专业程序员喜欢使用 Python,因为它是一种通用编程语言,因此允许您几乎无需费力地实现任何功能。初学者喜欢 Python,因为它比其他语言更容易学习。因此,Python 既用于临时数据分析和小型自动化任务,也用于像 Instagram 后端这样的大型生产代码库。6 这也意味着,当您的基于 Python 的 Excel 工具真正受欢迎时,很容易添加一个 Web 开发人员,将您的 Excel-Python 原型转换为完整的 Web 应用程序。Python 的独特优势在于,业务逻辑部分很可能不需要重新编写,而可以直接从 Excel 原型移植到生产 Web 环境中。
在本节中,我将介绍 Python 的核心概念,并与 Excel 和 VBA 进行比较。我将涉及代码可读性、Python 的标准库和包管理器、科学计算堆栈、现代语言特性以及跨平台兼容性。让我们首先深入探讨可读性!
可读性和可维护性
如果你的代码易读,那就意味着它容易跟随和理解——尤其是对于那些没有自己编写代码的外部人员来说。这使得查找错误和维护代码变得更加容易。这就是为什么《Python 之禅》中有一句话是“可读性至关重要”。《Python 之禅》是 Python 核心设计原则的简洁总结,在下一章节我们将学习如何打印它。让我们看一下以下的 VBA 代码片段:
If``i``<``5``Then``Debug``.``Print``"i is smaller than 5"``ElseIf``i``<=``10``Then``Debug``.``Print``"i is between 5 and 10"``Else``Debug``.``Print``"i is bigger than 10"``End``If
在 VBA 中,您可以将代码片段重新格式化为以下内容,这与原来的完全等价:
If``i``<``5``Then``Debug``.``Print``"i is smaller than 5"``ElseIf``i``<=``10``Then``Debug``.``Print``"i is between 5 and 10"``Else``Debug``.``Print``"i is bigger than 10"``End``If
在第一个版本中,视觉缩进与代码逻辑对齐。这使得代码易于阅读和理解,从而更容易发现错误。在第二个版本中,对于第一次浏览代码的新开发人员可能看不到ElseIf
和Else
条件——如果代码是大型代码库的一部分,这显然更为真实。
Python 不接受类似第二个示例那样格式化的代码:它强制你将视觉缩进与代码逻辑对齐,以防止可读性问题。Python 之所以能做到这一点,是因为它依赖缩进来定义代码块,例如在 if
语句或 for
循环中使用。与缩进不同,大多数其他语言使用花括号,而 VBA 则使用关键字如 End If
,就像我们刚刚在代码片段中看到的那样。使用缩进来定义代码块的原因是,在编程中,大部分时间都花在维护代码上,而不是最初编写代码。编写可读的代码有助于新程序员(或者是编写代码几个月后的自己)回过头去理解代码的运作方式。
我们将在第三章详细学习 Python 的缩进规则,但现在让我们继续探讨标准库:Python 开箱即用的功能。
标准库和包管理器
Python 自带丰富的功能集,由其标准库提供支持。Python 社区喜欢称其为“电池已内置”。无论您需要解压缩 ZIP 文件,读取 CSV 文件中的值,还是从互联网获取数据,Python 的标准库都可以胜任,通常只需几行代码即可完成。在 VBA 中,相同的功能需要大量代码或安装插件。而且,您在互联网上找到的解决方案通常只适用于 Windows 而非 macOS。
尽管 Python 的标准库涵盖了大量功能,但仍有一些任务在仅依赖标准库时编写起来很麻烦或者速度很慢。这就是PyPI发挥作用的地方。PyPI 是 Python 包索引的缩写,是一个巨大的仓库,任何人(包括您!)都可以上传开源 Python 包,为 Python 添加额外的功能。
PYPI VS. PYPY
PyPI 的发音是“派皮艾”,这是为了将 PyPI 与 PyPy 区分开来,PyPy 的发音是“派派”,它是 Python 的一个快速替代实现。
例如,为了更容易地从互联网上获取数据,您可以安装 Requests 包,以便获得一组强大且易于使用的命令。要安装它,您可以使用 Python 的包管理器 pip,在命令提示符或终端上运行。pip 是递归首字母缩略词,表示 pip 安装包。如果现在这听起来有点抽象,不要担心;我将在下一章详细解释它的工作原理。现在更重要的是理解为什么包管理器如此重要。其中一个主要原因是任何合理的包不仅依赖于 Python 的标准库,而且再次依赖于其他同样托管在 PyPI 上的开源包。这些依赖可能再次依赖于子依赖项等等。pip 递归检查包及其依赖项和子依赖项,并下载安装它们。pip 还可以轻松更新您的包,因此您可以保持依赖项最新。这使得遵循 DRY 原则更加容易,因为您不需要重复发明或复制/粘贴 PyPI 上已有的内容。通过 pip 和 PyPI,您还拥有一个坚实的机制来分发和安装这些依赖项,这是 Excel 传统插件所缺乏的。
开源软件(OSS)
此时,我想对开源软件说几句,因为在本节中我已经多次使用了这个词。如果软件在开源许可下分发,意味着其源代码可以免费获取,允许每个人贡献新功能、修复错误或文档。Python 本身以及几乎所有第三方 Python 包都是开源的,通常由开发者在业余时间维护。这并不总是一个理想的状态:如果您的公司依赖于某些包,您对这些包由专业程序员持续开发和维护感兴趣。幸运的是,科学计算 Python 社区已经意识到一些包太重要了,不能把它们的命运交给那些在晚上和周末工作的几个志愿者。
这就是为什么在 2012 年,NumFOCUS,一个非营利组织,成立了,以赞助科学计算领域的各种 Python 包和项目。NumFOCUS 赞助的最受欢迎的 Python 包包括 pandas、NumPy、SciPy、Matplotlib 和 Project Jupyter,但现在他们还支持来自各种其他语言,包括 R、Julia 和 JavaScript 的包。有一些大型企业赞助商,但每个人都可以作为自由社区成员加入 NumFOCUS —— 捐款可以免税。
使用 pip,您可以安装几乎任何类型的包,但对于 Excel 用户来说,最有趣的肯定是科学计算的包。让我们在下一节更多地了解使用 Python 进行科学计算!
科学计算
Python 成功的一个重要原因是它作为一种通用编程语言创建。后来添加了用于科学计算的能力,以第三方包的形式。这具有独特的优势,即数据科学家可以使用同一种语言进行实验和研究,而 Web 开发人员则可以最终围绕计算核心构建出生产就绪的应用程序。能够使用一种语言构建科学应用程序减少了摩擦、实施时间和成本。科学包如 NumPy、SciPy 和 pandas 使我们能够以非常简洁的方式制定数学问题。例如,让我们来看看根据现代投资组合理论计算投资组合方差的一个更著名的金融公式:
投资组合方差用符号表示为,其中是各个资产的权重向量,是投资组合的协方差矩阵。如果w
和C
是 Excel 范围,则可以在 VBA 中如下计算投资组合方差:
方差``=``Application``.``MMult``(``Application``.``MMult``(``Application``.``Transpose``(``w``),``C``),``w``)
比较这一点与 Python 中的几乎数学符号表示,假设w
和C
是 pandas 的 DataFrame 或 NumPy 数组(我将在第二部分正式介绍它们):
方差``=``w``.``T``@``C``@``w
但这不仅仅是美学和可读性问题:NumPy 和 pandas 在底层使用编译后的 Fortran 和 C 代码,这在处理大型矩阵时比 VBA 提供了性能提升。
在 VBA 中缺少对科学计算的支持显然是一个限制。但即使您看看核心语言特性,VBA 也已经落后了,我将在下一节中指出。
现代语言特性
自 Excel 97 以来,VBA 语言在语言特性方面没有任何重大变化。但这并不意味着 VBA 不再受支持:微软在每次 Excel 的新版本中都会发布更新,以便自动化该版本引入的新 Excel 功能。例如,Excel 2016 增加了支持自动化 Power Query 的功能。一个停止发展二十多年的语言缺少了随着年代推移在所有主要编程语言中引入的现代语言概念。例如,在 VBA 中处理错误的方式真的显得过时。如果您想在 VBA 中优雅地处理错误,就像这样:
Sub``PrintReciprocal``(``number``As``Variant``)``' 如果数字是 0 或字符串,将会发生错误``On``Error``GoTo``ErrorHandler``result``=``1``/``number``On``Error``GoTo``0``Debug``.``Print``"没有错误发生!"``Finally:``' 无论是否发生错误都会运行``If``result``=``""``Then``result``=``"N/A"``End``If``Debug``.``Print``"倒数是: "``&``result``Exit``Sub``ErrorHandler:``' 仅在发生错误时运行``Debug``.``Print``"发生了错误: "``&``Err``.``Description``Resume``Finally``End``Sub
VBA 错误处理涉及在示例中使用Finally
和ErrorHandler
这样的标签。您通过GoTo
或Resume
语句指示代码跳转到这些标签。早期,标签被认为是许多程序员所说的意大利面代码的原因:这是一种表达方式,意味着代码的流程难以跟踪,因此难以维护。这就是为什么几乎所有积极开发的语言都引入了try/catch
机制——在 Python 中称为try/except
——我将在第十一章中介绍它。如果您是一个熟练的 VBA 开发者,您可能也会喜欢 Python 支持类继承这一事实,这是面向对象编程中的一个特性,在 VBA 中缺少。
除了现代语言功能之外,现代编程语言还有另一个要求:跨平台兼容性。让我们看看这为何如此重要!
跨平台兼容性
即使您在运行 Windows 或 macOS 的本地计算机上开发代码,很可能最终希望在服务器或云中运行程序。服务器允许您的代码按计划执行,并使您的应用程序可以从任何您希望的地方访问,具备您需要的计算能力。事实上,在下一章中,我将向您介绍托管的 Jupyter 笔记本,展示如何在服务器上运行 Python 代码。绝大多数服务器运行 Linux,因为它是一种稳定、安全且具有成本效益的操作系统。而且由于 Python 程序在所有主要操作系统上都能无缝运行,这将大大减少从本地计算机转移到生产环境设置时的困扰。
相比之下,即使 Excel VBA 在 Windows 和 macOS 上运行,也很容易引入仅在 Windows 上运行的功能。在官方 VBA 文档或论坛上,您经常会看到类似以下代码:
Set``fso``=``CreateObject``(``"Scripting.FileSystemObject"``)
每当你使用CreateObject
调用或被告知在 VBA 编辑器中转到工具 > 引用以添加引用时,你几乎总是在处理只能在 Windows 上运行的代码。如果你希望你的 Excel 文件能够跨 Windows 和 macOS 正常工作,另一个需要注意的突出领域是 ActiveX 控件。ActiveX 控件是诸如按钮和下拉菜单之类的元素,你可以将它们放在工作表上,但它们只在 Windows 上运行。如果你希望你的工作簿也能在 macOS 上运行,请务必避免使用它们!
结论
在本章中,我们遇到了 Python 和 Excel,这两种非常流行的技术已经存在了多个十年——与我们今天使用的许多其他技术相比,这是很长一段时间了。伦敦鲸鱼事件作为一个例子,展示了当你在关键任务工作簿中没有正确使用 Excel 时可能会出现多大的问题(以美元计)。这激发了我们寻找一组最佳编程实践的动力:应用关注点分离,遵循 DRY 原则,并利用自动化测试和版本控制。然后,我们研究了 Power Query 和 Power Pivot,这是微软处理比你的电子表格更大的数据的方法。然而,我认为它们通常不是正确的解决方案,因为它们将你锁定在微软世界中,并阻止你利用现代基于云的解决方案的灵活性和强大功能。
Python 具有令人信服的特性,这些特性在 Excel 中缺失:标准库、包管理器、科学计算库和跨平台兼容性。通过学习如何将 Excel 与 Python 结合使用,你可以兼顾两者的优势,并通过自动化节省时间,在遵循编程最佳实践方面减少错误,并且如果有必要,可以将你的应用程序扩展到 Excel 之外的规模。
现在你知道为什么 Python 是 Excel 的强大伴侣,是时候设置你的开发环境,开始编写你的第一行 Python 代码了!
1 你可以在Excel 博客上阅读有关 lambda 函数发布的公告。
2 James Vincent,“科学家将人类基因重新命名,以防止 Microsoft Excel 将其误读为日期”,The Verge,2020 年 8 月 6 日,
oreil.ly/0qo-n
。3 Leo Kelion,“Excel:为什么使用微软工具导致 COVID-19 结果丢失”,BBC 新闻,2020 年 10 月 5 日,
oreil.ly/vvB6o
。4 维基百科在其有关该案例的文章的一个脚注中链接到该文档。
5 术语来自 Microsoft 应用程序架构指南第 2 版,可在线上获取。
6 你可以在他们的工程博客上了解更多有关 Instagram 如何使用 Python 的信息。
第二章:开发环境
你可能迫不及待地想要学习 Python 的基础知识,但在此之前,你需要相应地设置你的计算机。要编写 VBA 代码或 Power Queries,只需启动 Excel 并分别打开 VBA 或 Power Query 编辑器即可。而使用 Python,则需要更多的工作。
我们将从安装 Anaconda Python 发行版开始本章。除了安装 Python 外,Anaconda 还将为我们提供 Anaconda Prompt 和 Jupyter 笔记本两个基本工具,这两个工具我们将在本书中经常使用。Anaconda Prompt 是一个特殊的命令提示符(Windows)或终端(macOS);它允许我们运行 Python 脚本和其他本书中会遇到的命令行工具。Jupyter 笔记本允许我们以交互方式处理数据、代码和图表,这使得它们成为 Excel 工作簿的一个强大替代品。在使用了一段时间的 Jupyter 笔记本后,我们将安装 Visual Studio Code(VS Code),一个功能强大的文本编辑器。VS Code 非常适合编写、运行和调试 Python 脚本,并带有集成的终端。图 2-1 总结了 Anaconda 和 VS Code 中包含的内容。
由于本书主要讲解 Excel,因此本章将重点介绍 Windows 和 macOS。然而,包括第三部分在内的一切内容也适用于 Linux。让我们开始安装 Anaconda 吧!
图 2-1. 开发环境
Anaconda Python 发行版
Anaconda 可以说是用于数据科学最流行的 Python 发行版,预装了数百个第三方包:它包括 Jupyter 笔记本和本书中将广泛使用的大多数其他包,包括 pandas、OpenPyXL 和 xlwings。Anaconda 个人版可以免费私人使用,并保证所有包都与彼此兼容。它安装在一个单独的文件夹中,并且可以轻松地卸载。安装完毕后,我们将在 Anaconda Prompt 上学习一些基本命令,并运行一个交互式的 Python 会话。然后,我们将介绍包管理器 Conda 和 pip,最后以 Conda 环境结束本节。让我们开始下载并安装 Anaconda 吧!
安装
前往Anaconda 主页并下载最新版本的 Anaconda 安装程序(个人版)。确保下载 Python 3.x 版本的 64 位图形化安装程序。1 下载完成后,双击安装程序开始安装过程,并确保接受所有默认设置。有关更详细的安装说明,请参阅官方文档。
其他 Python 发行版
尽管本书的指导假设您已安装了 Anaconda 个人版,但所展示的代码和概念也适用于任何其他 Python 安装。在这种情况下,您需要按照配套存储库中 requirements.txt 中包含的说明安装所需的依赖项。
安装了 Anaconda 后,我们现在可以开始使用 Anaconda Prompt。让我们看看这是什么以及它是如何工作的!
Anaconda Prompt
Anaconda Prompt 实际上只是在 Windows 上的命令提示符和 macOS 上的终端,已设置为使用正确的 Python 解释器和第三方包运行。Anaconda Prompt 是运行 Python 代码的最基本工具,在本书中我们将广泛使用它来运行 Python 脚本和各种由各种包提供的命令行工具。
在没有 Anaconda 的情况下使用 ANACONDA PROMPT
如果您不使用 Anaconda Python 发行版,每当我指示您使用 Anaconda Prompt 时,您将不得不使用 Windows 上的命令提示符和 macOS 上的终端。
如果您从未在 Windows 上使用过命令提示符或在 macOS 上使用过终端,不用担心:您只需要知道几个命令,这些命令将为您提供大量功能。一旦习惯了,使用 Anaconda Prompt 通常比通过图形用户界面菜单点击更快更方便。让我们开始吧:
Windows
点击“开始”菜单按钮,然后开始输入
Anaconda Prompt
。在出现的条目中,选择 Anaconda Prompt,而不是 Anaconda Powershell Prompt。可以使用箭头键选择它并按 Enter 键,或者使用鼠标点击它。如果您喜欢通过“开始”菜单打开它,可以在 Anaconda3 下找到它。将 Anaconda Prompt 固定到 Windows 任务栏是一个好主意,因为您将在本书中经常使用它。Anaconda Prompt 的输入行将以(base)
开头:
(base) C:\Users\felix>
macOS
在 macOS 上,您找不到名为 Anaconda Prompt 的应用程序。相反,Anaconda Prompt 指的是由 Anaconda 安装程序设置的终端,用于自动激活 Conda 环境(稍后我会详细介绍 Conda 环境):按下 Command-Space 键或打开 Launchpad,然后键入
Terminal
并按 Enter 键。或者,打开 Finder 并导航到应用程序 > 实用工具,在那里您会找到可以双击的 Terminal 应用程序。一旦终端出现,它应该看起来像这样,即输入行必须以(base)
开头:
(base) felix@MacBook-Pro ~ %
如果你使用的是较旧版本的 macOS,它看起来会是这个样子:
(base) MacBook-Pro:~ felix$
与 Windows 上的命令提示符不同,macOS 上的终端不显示当前目录的完整路径。相反,波浪符号表示主目录,通常是 /Users/
。要查看当前目录的完整路径,请键入 pwd
然后按 Enter 键。pwd
表示打印工作目录。如果您在安装 Anaconda 后在终端中输入行不是以
(base)
开头,常见原因是:如果在 Anaconda 安装过程中终端处于运行状态,您需要重新启动它。请注意,点击终端窗口左上角的红色叉号只会隐藏它而不会退出它。相反,请右键单击 dock 中的终端,选择退出,或者在终端是活动窗口时按 Command-Q 键。重新启动后,如果终端显示(base)
开头的新行,表示已设置好。建议将终端固定在 dock 中,因为您将经常使用它。
在 Anaconda 提示符处,尝试执行 表 2-1 中概述的命令。我将在表后更详细地解释每个命令。
表 2-1. Anaconda 提示的命令
命令 | Windows | macOS |
---|---|---|
列出当前目录中的文件 | dir |
ls -la |
切换目录(相对路径) | cd path\to\dir |
cd path/to/dir |
切换目录(绝对路径) | cd C:\path\to\dir |
cd /path/to/dir |
切换到 D 驱动器 | D: |
(不存在) |
切换到上级目录 | cd .. |
cd .. |
浏览以前的命令 | ↑(上箭头) | ↑(上箭头) |
列出当前目录中的文件
在 Windows 上,输入
dir
查看目录内容,然后按 Enter 键。这将打印出您当前所在目录的内容。在 macOS 上,输入
ls -la
并按 Enter 键。ls
是列出目录内容的缩写,-la
将以长列表格式打印输出,并包括所有文件,包括隐藏文件。
切换目录
输入
cd Down
并按 Tab 键。cd
是改变目录的缩写。如果您在主文件夹中,Anaconda 提示应该能够自动完成到cd Downloads
。如果您在不同的文件夹中,或者没有名为 Downloads 的文件夹,只需在按 Tab 键自动完成之前,开始键入前一个命令 (dir
或ls -la
) 中看到的一个目录名的开始部分。然后按 Enter 键以进入自动完成的目录。如果您在 Windows 上需要更改驱动器,请先输入驱动器名称,然后才能更改正确的目录:
C:\Users\felix>
D:
D:\>
cd data
D:\data>
注意,如果您从当前目录开始,使用相对路径启动路径时,需要使用一个位于当前目录中的目录或文件名,例如,
cd Downloads
。如果您想要跳出当前目录,可以输入绝对路径,例如,在 Windows 上是cd C:\Users
,在 macOS 上是cd /Users
(请注意斜杠是在路径开头的)。
切换到上级目录
要进入父目录,即在目录层次结构中向上移动一级,请输入
cd ..
,然后按 Enter 键(确保在cd
和点之间有一个空格)。如果你想结合目录名称使用这个命令,例如,如果你想向上移动一级,然后切换到桌面目录,输入cd ..\Desktop
。在 macOS 上,请用正斜杠替换反斜杠。
浏览之前的命令
使用向上箭头键浏览之前的命令。如果你需要多次运行相同的命令,这将节省大量击键时间。如果向上滚动太远,使用向下箭头键回滚。
文件扩展名
不幸的是,在 Windows 资源管理器或 macOS Finder 中,默认隐藏文件扩展名。这可能会使处理 Python 脚本和 Anaconda Prompt 变得更加困难,因为你需要引用包括其扩展名的文件。在处理 Excel 时,显示文件扩展名还有助于你了解你正在处理的是默认的 xlsx 文件,还是启用宏的 xlsm 文件,或任何其他 Excel 文件格式。以下是如何使文件扩展名可见的方法:
Windows
打开文件资源管理器,点击“视图”选项卡。在“显示/隐藏”组下,勾选“文件扩展名”复选框。
macOS
打开 Finder,并通过按下 Command-,(Command-逗号键) 进入偏好设置。在高级选项卡上,勾选“显示所有文件扩展名”旁边的复选框。
到此为止!你现在可以启动 Anaconda Prompt 并在所需目录中运行命令。你将立即在下一节中使用这个功能,我将向你展示如何启动交互式 Python 会话。
Python REPL:交互式 Python 会话
你可以通过在 Anaconda Prompt 上运行 python
命令来启动交互式 Python 会话:
(base) C:\Users\felix>``python
Python 3.8.5 (default, Sep 3 2020, 21:29:08) [...] :: Anaconda, Inc. on win32 Type "help", "copyright", "credits" or "license" for more information. >>>
在 macOS 终端打印的文本略有不同,但功能基本相同。本书基于 Python 3.8 — 如果你想使用更新的 Python 版本,请确保参考书籍主页上的说明。
ANACONDA PROMPT NOTATION
今后,我将以
(base)>
开头来标记 Anaconda Prompt 中输入的代码行。例如,要启动交互式 Python 解释器,我将这样写:
(base)>
python
而在 Windows 上看起来类似于这样:
(base) C:\Users\felix>
python
在 macOS 上类似这样(请记住,在 macOS 上,终端即为你的 Anaconda Prompt):
(base) felix@MacBook-Pro ~ %
python
让我们玩一玩吧!请注意,在交互式会话中,>>>
表示 Python 正在等待你的输入;你无需输入这些字符。跟着我输入每一行以 >>>
开头的代码,并按 Enter 键确认:
>>>
3 + 4
7 >>>
"python " * 3
'python python python '
这个交互式 Python 会话也称为 Python REPL,即读取-求值-打印循环:Python 读取你的输入,计算它,并立即打印结果,同时等待你的下一个输入。还记得我在前一章提到的 Python 之禅吗?现在你可以阅读完整版本,了解 Python 的指导原则(包括微笑)。只需在键入此行后按 Enter 运行:
>>>
import this
要退出 Python 会话,请键入 quit()
,然后按 Enter 键。或者,在 Windows 上按 Ctrl+Z,然后按 Enter 键。在 macOS 上,只需按 Ctrl+D — 无需按 Enter。
退出 Python REPL 后,现在是玩转 Conda 和 pip 的好时机,这两个是随 Anaconda 安装提供的包管理器。
包管理器:Conda 和 pip
在上一章中,我已经介绍了 pip,Python 的包管理器:pip 负责下载、安装、更新和卸载 Python 包及其依赖关系和子依赖关系。虽然 Anaconda 可与 pip 配合使用,但它还有一种内置的替代包管理器称为 Conda。Conda 的一个优势是它不仅可以安装 Python 包,还可以安装 Python 解释器的其他版本。简而言之:包添加了额外的功能到你的 Python 安装中,这些功能不在标准库中。pandas 是这样一个包的例子,我将在第 5 章中详细介绍它。由于它已预装在 Anaconda 的 Python 安装中,你不必手动安装它。
CONDA 与 PIP
在 Anaconda 中,你应该尽可能通过 Conda 安装所有内容,并仅在 Conda 找不到这些包时使用 pip 安装。否则,Conda 可能会覆盖之前用 pip 安装的文件。
表格 2-2 提供了你将经常使用的命令的概述。这些命令必须在 Anaconda 提示符中键入,将允许你安装、更新和卸载第三方包。
表格 2-2. Conda 和 pip 命令
操作 | Conda | pip |
---|---|---|
列出所有已安装的包 | conda list |
pip freeze |
安装最新的包版本 | conda install package |
pip install package |
安装特定版本的包 | conda install package=1.0.0 |
pip install package==1.0.0 |
更新包 | conda update package |
pip install --upgrade package |
卸载包 | conda remove package |
pip uninstall package |
例如,要查看你的 Anaconda 发行版中已有哪些包,输入以下命令:
(base)>
conda list
每当本书需要一个不包含在 Anaconda 安装中的包时,我都会明确指出这一点,并向您展示如何安装它。但现在安装缺少的包可能是个好主意,这样以后就不必再处理它了。让我们首先安装 plotly 和 xlutils,这些包可以通过 Conda 获取:
(base)>
conda install plotly xlutils
运行此命令后,Conda 将显示它将要执行的操作,并要求您键入 y
并按回车键确认。完成后,您可以使用 pip 安装 pyxlsb 和 pytrends,因为这些包无法通过 Conda 获取:
(base)>
pip install pyxlsb pytrends
不像 Conda,当你按下回车键时,pip 会立即安装包而无需确认。
包版本
许多 Python 包经常更新,有时会引入不向后兼容的更改。这可能会破坏本书中的一些示例。我会尽力跟上这些变化,并在书的主页上发布修复内容,但你也可以创建一个使用我在写本书时使用的相同版本的包的 Conda 环境。我将在下一节介绍 Conda 环境,并在附录 A 中找到关于如何创建具有特定包的 Conda 环境的详细说明。
现在你知道如何使用 Anaconda Prompt 启动 Python 解释器并安装额外的包了。在下一节中,我会解释你的 Anaconda Prompt 开头的(base)
是什么意思。
Conda 环境
你可能一直在想为什么 Anaconda Prompt 在每个输入行的开头显示 (base)
。这是活跃 Conda 环境的名称。Conda 环境是一个单独的“Python 世界”,具有特定版本的 Python 和一组安装了特定版本的包。为什么需要这个?当你同时在不同项目上工作时,它们会有不同的需求:一个项目可能使用 Python 3.8 和 pandas 0.25.0,而另一个项目可能使用 Python 3.9 和 pandas 1.0.0。为 pandas 0.25.0 编写的代码通常需要更改才能运行 pandas 1.0.0,所以你不能只升级你的 Python 和 pandas 版本而不修改代码。为每个项目使用 Conda 环境确保每个项目都使用正确的依赖关系。虽然 Conda 环境是特定于 Anaconda 分发的,但该概念在每个 Python 安装下以虚拟环境的名称存在。Conda 环境更强大,因为它们使处理不同版本的 Python 本身更容易,而不仅仅是包。
在阅读本书时,你无需更改你的 Conda 环境,因为我们将始终使用默认的 base
环境。但是,当你开始构建真实项目时,最好为每个项目使用一个 Conda 或虚拟环境,以避免它们的依赖关系之间可能发生的冲突。有关处理多个 Conda 环境的所有信息都在附录 A 中解释了。在那里,你还将找到有关使用我用来撰写本书的确切软件包版本创建 Conda 环境的说明。这将使你能够在未来很多年内按原样运行本书中的示例。另一个选择是关注书的主页,以获取可能需要针对较新版本的 Python 和软件包进行的更改。
解 解决了关于 Conda 环境的谜团后,现在是介绍下一个工具的时候了,这个工具在本书中会被广泛使用:Jupyter 笔记本!
Jupyter 笔记本
在上一节中,我向你展示了如何从 Anaconda 提示符启动一个交互式的 Python 会话。如果你想要一个简单的环境来测试一些简单的东西,这是很有用的。然而,对于大多数工作,你希望一个更易于使用的环境。例如,使用 Anaconda 提示符中运行的 Python REPL 很难返回到以前的命令并显示图表。幸运的是,Anaconda 带有比只有 Python 解释器更多的东西:它还包括 Jupyter 笔记本,这已经成为在数据科学环境中运行 Python 代码的最流行方式之一。Jupyter 笔记本允许你通过将可执行的 Python 代码与格式化文本、图片和图表结合到一个在浏览器中运行的交互式笔记本中来讲述一个故事。它们适合初学者,因此在你的 Python 旅程的最初阶段特别有用。然而,它们也非也非常受欢迎,用于教学、原型设计和研究,因为它们有助于实现可重复研究。
Jupyter 笔记本已经成为 Excel 的一个严肃竞争对手,因为它们覆盖了与工作簿大致相同的用例:你可以快速准备、分析和可视化数据。与 Excel 的区别在于,所有这些都是通过编写 Python 代码而不是在 Excel 中用鼠标点击来完成的。另一个优势是,Jupyter 笔记本不会混合数据和业务逻辑:Jupyter 笔记本保存了你的代码和图表,而你通常是从外部 CSV 文件或数据库中获取数据。在你的笔记本中显示 Python 代码使得你能够很容易地看到发生了什么,而在 Excel 中,公式被隐藏在单元格的值后面。Jupyter 笔记本在本地和远程服务器上运行都很容易。服务器通常比你的本地计算机性能更强,可以完全无人值守地运行你的代码,这在 Excel 中很难做到。
在本节中,我将向您展示如何运行和导航 Jupyter 笔记本的基础知识:我们将了解笔记本单元格的内容,并看看编辑模式和命令模式之间的区别。然后我们将理解为什么单元格的运行顺序很重要,然后在结束本节之前,我们将学习如何正确关闭笔记本。让我们从我们的第一个笔记本开始吧!
运行 Jupyter 笔记本
在 Anaconda Prompt 上,切换到您的伴侣存储库的目录,然后启动 Jupyter 笔记本服务器:
(base)>
cd C:\Users\``username``\python-for-excel
(base)>
jupyter notebook
这将自动打开您的浏览器,并显示带有目录中文件的 Jupyter 仪表板。在 Jupyter 仪表板的右上角,点击 New,然后从下拉列表中选择 Python 3(见图 2-2](#filepos133397))。
图 2-2. Jupyter 仪表板
这将在新的浏览器标签页中打开您的第一个空白 Jupyter 笔记本,如图 2-3](#filepos133850) 所示。
图 2-3. 一个空白的 Jupyter 笔记本
点击 Jupyter 标志旁边的 Untitled1 可以重命名工作簿为更有意义的名称,例如 first_notebook。图 2-3 的下部显示了一个笔记本单元格——继续下一节以了解更多信息!
笔记本单元格
在 图 2-3 中,您将看到一个空的单元格,其中有一个闪烁的光标。如果光标不闪烁,请用鼠标点击单元格的右侧,即在 In [ ]
的右侧。现在重复上一节的练习:键入 3 + 4
并通过点击顶部菜单栏中的运行按钮或更容易的方式——按 Shift+Enter 运行单元格。这将运行单元格中的代码,将结果打印在单元格下方,并跳转到下一个单元格。在这种情况下,它会插入一个空单元格,因为我们目前只有一个单元格。稍微详细解释一下:当单元格计算时,它显示 In [*]
,当计算完成时,星号变成一个数字,例如 In [1]
。在单元格下方,您将看到相应的输出,标有相同的数字:Out [1]
。每次运行一个单元格,计数器都会增加一,这有助于您查看单元格执行的顺序。在接下来的内容中,我将以这种格式展示代码示例,例如之前的 REPL 示例看起来是这样的:
In``[``1``]:``3``+``4
Out[1]: 7
这种表示法允许您在笔记本单元格中键入 3 + 4
跟随而来。通过按 Shift+Enter 运行它,您将得到我展示的输出 Out[1]
。如果您在支持颜色的电子格式中阅读本书,您会注意到输入单元格使用不同颜色格式化字符串、数字等,以便更容易阅读。这被称为语法高亮显示。
CELL OUTPUT
如果单元格中的最后一行返回一个值,Jupyter 笔记本会自动在
Out [ ]
下打印它。但是,当你使用In
单元格下打印,而不带有Out [ ]
标签。本书中的代码示例按此方式进行格式化。
单元格可以具有不同的类型,其中我们感兴趣的是两种:
代码
这是默认类型。每当你想运行 Python 代码时,请使用它。
Markdown
Markdown 是一种使用标准文本字符进行格式化的语法,可用于在笔记本中包含精美格式的解释和说明。
要将单元格类型更改为 Markdown,选择单元格,然后在单元格模式下拉菜单中选择 Markdown(参见图 2-3)。我会展示一个快捷键,用于在表 2-3 中更改单元格模式。在将空单元格更改为 Markdown 单元格后,键入以下文本,其中解释了几条 Markdown 规则:
# 这是一级标题 ## 这是二级标题 你可以使你的文本 *斜体* 或 **粗体** 或 `等宽字体`。 * 这是一个项目符号 * 这是另一个项目符号
按下 Shift+Enter 后,文本将呈现为精美格式化的 HTML。此时,你的笔记本应该看起来像图 2-4 中的内容一样。Markdown 单元格还允许你包含图片、视频或公式;请参阅Jupyter 笔记本文档。
图 2-4. 运行代码单元格和 Markdown 单元格后的笔记本
现在你已经了解了代码和 Markdown 单元格类型,是时候学习更简单的在单元格之间导航的方法了:下一节介绍了编辑和命令模式以及一些键盘快捷键。
编辑模式与命令模式
在 Jupyter 笔记本中与单元格交互时,你要么处于编辑模式,要么处于命令模式:
编辑模式
点击单元格会进入编辑模式:所选单元格周围的边框变为绿色,并且单元格内的光标在闪烁。你也可以在选定单元格时按 Enter 键,而不是点击单元格。
命令模式
要切换到命令模式,请按下
Escape
键;所选单元格周围的边框将变为蓝色,且不会有任何闪烁的光标。你可以在命令模式下使用的最重要的键盘快捷键如表 2-3 所示。
表 2-3. 键盘快捷键(命令模式)
快捷键 | 动作 |
---|---|
Shift+Enter | 运行单元格(在编辑模式下也适用) |
↑(向上箭头 ) |
向上移动单元格选择器 |
↓(向下箭头 ) |
向下移动单元格选择器 |
b |
在当前单元格下方插入一个新单元格 |
a |
在当前单元格上方插入一个新单元格 |
dd |
删除当前单元格(键入两次字母d ) |
m |
将单元格类型更改为 Markdown |
y |
将单元格类型更改为代码 |
知道这些键盘快捷键将允许你高效地使用笔记本,而无需经常在键盘和鼠标之间切换。在下一节中,我将向你展示使用 Jupyter 笔记本时需要注意的常见陷阱:按顺序运行单元格的重要性。
运行顺序很重要
虽然笔记本易于上手和用户友好,但如果不按顺序运行单元格,也容易陷入混乱的状态。假设你有以下笔记本单元格按顺序运行:
`In
[
2]:
a=
1```In
[
3]:
a``
Out[3]: 1
In``[``4``]:``a``=``2
单元格Out[3]
按预期输出值1
。然而,如果现在返回并再次运行In[3]
,你将会陷入这种情况:
In``[``2``]:``a``=``1
`In
[
5]:
a``
Out[5]: 2
In``[``4``]:``a``=``2
Out[5]
现在显示的值为2
,这可能不是你在从顶部阅读笔记本时期望的结果,特别是如果In[4]
单元格更远,需要向下滚动。为防止这种情况发生,我建议你不仅重新运行单个单元格,还应该重新运行其前面的所有单元格。Jupyter 笔记本为你提供了一个简单的方法,在菜单单元格 > 运行所有上面可以实现这一点。在这些警示之后,让我们看看如何正确关闭笔记本!
关闭 Jupyter 笔记本
每个笔记本在单独的 Jupyter 内核中运行。内核是运行你在笔记本单元格中键入的 Python 代码的“引擎”。每个内核都会使用操作系统的 CPU 和 RAM 资源。因此,当你关闭一个笔记本时,应同时关闭其内核,以便资源可以被其他任务再次使用,这将防止系统变慢。最简单的方法是通过文件 > 关闭并停止关闭笔记本。如果只是关闭浏览器选项卡,内核将不会自动关闭。另外,在 Jupyter 仪表板上,你可以通过运行标签关闭正在运行的笔记本来关闭笔记本。
要关闭整个 Jupyter 服务器,请单击 Jupyter 仪表板右上角的退出按钮。如果已经关闭了浏览器,你可以在运行笔记本服务器的 Anaconda 提示符中键入两次 Ctrl+C,或者完全关闭 Anaconda 提示符。
云中的 Jupyter 笔记本
Jupyter 笔记本已经变得如此流行,以至于各种云提供商都提供它们作为托管解决方案。我在这里介绍三个免费使用的服务。这些服务的优势在于它们可以即时在任何你可以访问浏览器的地方运行,而无需在本地安装任何内容。例如,你可以在阅读前三部分的同时,在平板电脑上运行示例。但是,由于第四部分需要本地安装 Excel,所以在那里无法运行。
绑定器
Binder 是 Jupyter 笔记本背后的组织 Project Jupyter 提供的服务。Binder 旨在尝试来自公共 Git 仓库的 Jupyter 笔记本 - 你不在 Binder 本身存储任何东西,因此你不需要注册或登录即可使用它。
Kaggle 笔记本
Kaggle 是一个数据科学平台。它主办数据科学竞赛,让你轻松获取大量数据集。自 2017 年起,Kaggle 已成为谷歌的一部分。
Google Colab
Google Colab(简称 Colaboratory)是 Google 的笔记本平台。不幸的是,大多数 Jupyter 笔记本的键盘快捷键不起作用,但你可以访问你的 Google Drive 上的文件,包括 Google Sheets。
在云端运行伴随仓库的 Jupyter 笔记本最简单的方法是访问其Binder URL。你将在伴随仓库的副本上工作,所以随意编辑和打破东西!
现在您已经知道如何使用 Jupyter 笔记本工作,让我们继续学习如何编写和运行标准的 Python 脚本。为此,我们将使用 Visual Studio Code,这是一个功能强大的文本编辑器,支持 Python。
Visual Studio Code
在本节中,我们将安装和配置 Visual Studio Code(VS Code),这是来自 Microsoft 的免费开源文本编辑器。在介绍其最重要的组件后,我们将以几种不同的方式编写第一个 Python 脚本并运行它。不过,首先我将解释为什么我们将使用 Jupyter 笔记本而不是运行 Python 脚本,并且为什么我选择了 VS Code。
虽然 Jupyter 笔记本非常适合互动式工作流,如研究、教学和实验,但如果你想编写不需要笔记本的可视化能力的 Python 脚本,用于生产环境,它们就不那么理想了。此外,使用 Jupyter 笔记本管理涉及多个文件和开发者的更复杂项目也很困难。在这种情况下,你应该使用适合的文本编辑器来编写和运行经典的 Python 文件。理论上,你可以使用几乎任何文本编辑器(甚至记事本也可以),但实际上,你需要一个“理解”Python 的文本编辑器。也就是说,一个至少支持以下功能的文本编辑器:
语法高亮
根据单词表示的是否是函数、字符串、数字等,编辑器会用不同的颜色标记这些词,这样更容易阅读和理解代码。
自动补全
自动补全或 IntelliSense,正如微软所称,会自动建议文本组件,从而让您更少输入,减少错误。
而且不久之后,你会希望直接从编辑器中访问其他需求:
运行代码
在文本编辑器和外部 Anaconda Prompt(即命令提示符或终端)之间来回切换以运行代码可能会很麻烦。
调试器
调试器允许你逐行步进代码,查看发生了什么。
版本控制
如果你使用 Git 进行版本控制,直接在编辑器中处理与 Git 相关的事务是有意义的,这样你就不必在两个应用程序之间来回切换。
有一系列工具可以帮助你完成所有这些工作,如往常,每个开发人员都有不同的需求和偏好。有些人可能确实想要使用一个简单的文本编辑器,配合一个外部命令提示符。而其他人可能更喜欢集成开发环境(IDE):IDE 试图将你所需的一切都放入一个工具中,这可能会使它们变得臃肿。
我选择了 VS Code 作为本书的工具,因为它在 2015 年首次发布后迅速成为开发人员中最受欢迎的代码编辑器之一:在 StackOverflow 开发者调查 2019 中,它被评为最受欢迎的开发环境。是什么让 VS Code 成为如此受欢迎的工具呢?实质上,它是一个简易文本编辑器和完整 IDE 之间的完美结合:VS Code 是一个迷你 IDE,一切都已经包含在内,但不再多余:
跨平台
VS Code 可在 Windows、macOS 和 Linux 上运行。还有云托管版本,比如GitHub Codespaces。
集成工具
VS Code 自带调试器,支持 Git 版本控制,并且有一个集成的终端,你可以将其用作 Anaconda Prompt。
扩展
其他所有内容,比如 Python 支持,都是通过单击安装的扩展添加的。
轻量级
根据你的操作系统,VS Code 安装程序的大小仅为 50–100 MB。
Visual Studio Code 与 Visual Studio 的区别
不要将 Visual Studio Code 与 Visual Studio 混淆!虽然你可以使用 Visual Studio 进行 Python 开发(它附带了 PTVS,即 Python Tools for Visual Studio),但它是一个非常庞大的安装包,传统上是用于处理 .NET 语言,比如 C#。
要想知道你是否同意我对 VS Code 的赞美,最好的方法莫过于安装它并亲自试用一番。下一节将带你开始使用!
安装和配置
从VS Code 主页下载安装程序。有关最新的安装说明,请始终参考官方文档。
Windows
双击安装程序并接受所有默认设置。然后通过 Windows 开始菜单打开 VS Code,在那里你会在 Visual Studio Code 下找到它。
macOS
双击 ZIP 文件以解压应用程序。然后将 Visual Studio Code.app 拖放到应用程序文件夹中:现在你可以从启动台启动它了。如果应用程序没有启动,请转到系统偏好设置 > 安全性与隐私 > 通用,并选择「仍然打开」。
第一次打开 VS Code 时,它看起来像图 2-5。请注意,我已将默认的深色主题切换为浅色主题,以便更容易阅读截图。
图 2-5. Visual Studio Code
活动栏
在左侧,你会看到活动栏,从上到下的图标依次为:
-
资源管理器
-
-
搜索
-
-
源代码控制
-
-
运行
-
-
扩展
状态栏
在编辑器底部,你有状态栏。一旦配置完成并编辑一个 Python 文件,你将看到 Python 解释器显示在那里。
命令面板
你可以通过 F1 或键盘快捷键 Ctrl+Shift+P(Windows)或 Command-Shift-P(macOS)显示命令面板。如果有不确定的事情,你首先应该去命令面板,因为它可以轻松访问几乎所有你可以在 VS Code 中做的事情。例如,如果你正在寻找键盘快捷键,输入
keyboard shortcuts
,选择“Help: Keyboard Shortcuts Reference”并按 Enter。
VS Code 是一个开箱即用的优秀文本编辑器,但是要使其与 Python 协同工作良好,还需要进行一些配置:点击活动栏上的 Extensions 图标,搜索 Python。安装显示 Microsoft 为作者的官方 Python 扩展。安装需要一些时间,安装完成后,可能需要点击 Reload Required 按钮完成配置 — 或者你也可以完全重新启动 VS Code。根据你的平台完成配置:
Windows
打开命令面板并输入
default shell
。选择“Terminal: Select Default Shell”并按 Enter。在下拉菜单中选择 Command Prompt,并确认按 Enter。这是必需的,否则 VS Code 无法正确激活 Conda 环境。
macOS
打开命令面板并输入
shell command
。选择“Shell Command: Install ‘code’ command in PATH”并按 Enter。这是必需的,这样你就可以方便地从 Anaconda Prompt(即终端)启动 VS Code。
现在 VS Code 已经安装和配置好了,让我们使用它来编写并运行我们的第一个 Python 脚本!
运行 Python 脚本
尽管你可以通过 Windows 的“开始”菜单或 macOS 的“启动台”打开 VS Code,但是通过 Anaconda Prompt 打开 VS Code 通常更快,你可以通过 code
命令启动它。因此,打开一个新的 Anaconda Prompt,并使用 cd
命令切换到你想要工作的目录,然后指示 VS Code 打开当前目录(用点表示):
(base)>
cd C:\Users\``username``\python-for-excel
(base)>
code .
通过这种方式启动 VS Code 将会导致活动栏上的资源管理器自动显示运行 code
命令时所在目录的内容。
或者,您也可以通过 File > Open Folder 打开目录(在 macOS 上为 File > Open),但在我们在第四部分开始使用 xlwings 时,这可能会在 macOS 上引起权限错误。当您在活动栏的资源管理器上悬停文件列表时,您会看到新文件按钮出现,如 图 2-6 所示。单击新文件并命名为 hello_world.py,然后按 Enter。一旦在编辑器中打开,请写入以下代码行:
print``(``"hello world!"``)
请记住,Jupyter 笔记本方便地自动打印最后一行的返回值?当您运行传统的 Python 脚本时,您需要明确告诉 Python 要打印什么,这就是为什么在这里需要使用 print
函数的原因。在状态栏中,您现在应该看到您的 Python 版本,例如,“Python 3.8.5 64-bit (conda)”。如果单击它,命令面板将打开,并允许您选择不同的 Python 解释器(如果您有多个,包括 Conda 环境)。您的设置现在应该看起来像 图 2-6 中的设置。
图 2-6. 打开 hello_world.py 的 VS Code 界面
在运行脚本之前,请确保按下 Windows 上的 Ctrl+S 或 macOS 上的 Command-S 保存它。在 Jupyter 笔记本中,我们可以简单地选择一个单元格,然后按 Shift+Enter 运行该单元格。在 VS Code 中,您可以从 Anaconda Prompt 或点击运行按钮运行代码。从 Anaconda Prompt 运行 Python 代码是您可能在服务器上运行脚本的方式,因此了解这个过程非常重要。
Anaconda Prompt
打开 Anaconda Prompt,
cd
到包含脚本的目录,然后像这样运行脚本:
(base)>
cd C:\Users\``username``\python-for-excel
(base)>
python hello_world.py
hello world!
最后一行是脚本打印的输出。请注意,如果您不在与 Python 文件相同的目录中,您需要使用完整路径到您的 Python 文件:
(base)>
python C:\Users\``username``\python-for-excel\hello_world.py
hello world!
Anaconda Prompt 上的长文件路径
处理长文件路径的便捷方式是将文件拖放到 Anaconda Prompt 中。这将在光标所在处写入完整路径。
VS Code 中的 Anaconda Prompt
您无需切换到 Anaconda Prompt 就能使用它:VS Code 有一个集成的终端,您可以通过键盘快捷键 Ctrl+` 或通过 View > Terminal 打开。由于它在项目文件夹中打开,您无需先更改目录:
(base)>
python hello_world.py
hello world!
在 VS Code 中的运行按钮
在 VS Code 中,有一种简单的方法可以运行代码,而不必使用 Anaconda Prompt:当您编辑 Python 文件时,您将在右上角看到一个绿色的播放图标——这是运行文件按钮,如 图 2-6 所示。单击它将自动在底部打开终端并在那里运行代码。
在 VS Code 中打开文件
当你在资源管理器(活动栏)中单击文件时,VS Code 有一个不同寻常的默认行为:文件会以预览模式打开,这意味着你接下来单击的文件将替换它在标签中的位置,除非你对文件进行了一些更改。如果你想关闭单击行为(这样单击将选择文件,双击将打开它),请转到“首选项” > “设置”(在 Windows 上按 Ctrl+, 或在 macOS 上按 Command-,)并将“工作台”下拉菜单下的“列表:打开模式”设置为“双击”。
到目前为止,你已经知道如何在 VS Code 中创建、编辑和运行 Python 脚本。但是 VS Code 还可以做更多:在 附录 B 中,我解释了如何使用调试器以及如何在 VS Code 中运行 Jupyter 笔记本。
替代文本编辑器和 IDE
工具是个人的选择,仅因为本书基于 Jupyter 笔记本和 VS Code,并不意味着你不应该看看其他选项。
一些流行的文本编辑器包括:
Sublime Text
Sublime 是一个快速的商业文本编辑器。
Notepad++
Notepad++ 是免费的,已经存在很长时间,但只能在 Windows 上使用。
Vim 或 Emacs
Vim 或者 Emacs 对于初学者程序员来说可能不是最佳选择,因为它们的学习曲线陡峭,但它们在专业人士中非常受欢迎。这两款免费编辑器之间的竞争是如此激烈,以至于维基百科将其描述为“编辑器之战”。
流行的 IDE 包括:
PyCharm
PyCharm 社区版是免费且功能强大的,而专业版是商业版,增加了对科学工具和 Web 开发的支持。
Spyder
Spyder 类似于 MATLAB 的 IDE,并带有一个变量资源管理器。由于它包含在 Anaconda 分发中,你可以在 Anaconda Prompt 上运行以下命令进行尝试:
(base)>
spyder
。JupyterLab
JupyterLab 是由 Jupyter 笔记本团队开发的基于 Web 的 IDE,当然,它也可以运行 Jupyter 笔记本。除此之外,它还试图将你在数据科学任务中所需的一切整合到一个工具中。
Wing Python IDE
Wing Python IDE 是一个存在很长时间的 IDE。有免费的简化版本和一个商业版本称为 Wing Pro。
Komodo IDE
Komodo IDE 是由 ActiveState 开发的商业 IDE,除了 Python 之外还支持许多其他语言。
PyDev
PyDev 是基于流行的 Eclipse IDE 的 Python IDE。
结论
在本章中,我向你展示了如何安装和使用我们将要使用的工具:Anaconda Prompt、Jupyter 笔记本和 VS Code。我们还在 Python REPL、Jupyter 笔记本和 VS Code 中运行了一小部分 Python 代码。
我建议你熟悉 Anaconda Prompt,因为一旦你习惯了它,它会给你带来很大的帮助。在云端使用 Jupyter 笔记本的能力也非常方便,因为它允许你在浏览器中运行本书前三部分的代码示例。
有了一个工作的开发环境,现在你已经准备好了解接下来的章节,这里你将学到足够的 Python 知识,以便能够跟上本书的其余内容。
1 32 位系统仅存在于 Windows 中,并且已经变得很少见。找出你的 Windows 版本的简单方法是在文件资源管理器中转到 C:\驱动器。如果你可以看到 Program Files 和 Program Files (x86)文件夹,那么你使用的是 64 位版本的 Windows。如果你只能看到 Program Files 文件夹,那么你使用的是 32 位系统。
第三章:开始学习 Python
安装了 Anaconda 并且 Jupyter 笔记本已经运行起来后,你就准备好开始使用 Python 了。虽然本章不会深入探讨基础之外的内容,但它仍然涵盖了很多内容。如果你刚开始学习编程,可能需要消化的内容很多。然而,大多数概念在后面的章节中通过实际示例使用后会变得更加清晰,因此如果第一次理解不完全,也不用担心。每当 Python 和 VBA 有显著区别时,我会指出来,以确保你能够顺利从 VBA 过渡到 Python,并了解显而易见的陷阱。如果你以前没有接触过 VBA,可以忽略这些部分。
我将从 Python 的基本数据类型开始,比如整数和字符串。接下来,我会介绍索引和切片,这是 Python 中的核心概念,它让你可以访问序列的特定元素。接着是像列表和字典这样可以容纳多个对象的数据结构。我会继续介绍if
语句以及for
和while
循环,然后介绍允许你组织和结构化代码的函数和模块。最后,我会展示如何正确格式化你的 Python 代码。到目前为止,你可能已经猜到,这一章的技术性非常强。在 Jupyter 笔记本中运行示例对于使一切更加互动和有趣是个不错的主意。你可以自己输入示例,也可以使用伴随存储库中提供的笔记本来运行它们。
数据类型
Python 和其他编程语言一样,通过为它们分配不同的数据类型来区分数字、文本、布尔值等。我们经常使用的数据类型包括整数、浮点数、布尔值和字符串。在这一部分,我将逐个介绍它们,并举几个例子。然而,要理解数据类型,首先需要解释对象是什么。
对象
在 Python 中,一切都是对象,包括数字、字符串、函数以及我们在本章中将遇到的所有内容。通过提供对一组变量和函数的访问,对象可以通过使复杂的事情变得简单和直观来帮助你。因此,在任何其他事情之前,让我先介绍一些关于变量和函数的内容!
变量
在 Python 中,变量是通过使用等号将名称分配给对象来定义的。在以下示例的第一行中,名称a
被分配给对象3
:
In``[``1``]:``a``=``3``b``=``4``a``+``b
Out[1]: 7
对于所有对象来说,这在 Python 中都是相同的,相比之下,VBA 更简单,你可以使用等号表示数字和字符串等数据类型,使用Set
语句表示像工作簿或工作表这样的对象。在 Python 中,你可以通过将变量分配给一个新对象来简单地改变变量的类型。这被称为动态类型:
In``[``2``]:``a``=``3``print``(``a``)``a``=``"three"``print``(``a``)
3 three
与 VBA 不同,Python 是区分大小写的,所以a
和A
是两个不同的变量。变量名必须遵循某些规则:
-
它们必须以字母或下划线开头
-
-
它们必须由字母、数字和下划线组成
在这个关于变量的简短介绍之后,让我们看看如何进行函数调用!
函数
我将在本章后面更详细地介绍函数。现在,你只需要知道如何调用内置函数,比如我们在前面代码示例中使用的print
。要调用函数,你需要在函数名后加上括号,并在括号内提供参数,这几乎相当于数学表示法:
function_name``(``argument1``,``argument2``,``...``)
现在让我们看看在对象的上下文中变量和函数是如何工作的!
属性和方法
在对象的上下文中,变量被称为属性,函数被称为方法:属性允许你访问对象的数据,而方法允许你执行操作。要访问属性和方法,你可以使用点号表示法,如myobject.attribute
和myobject.method()
。
让我们具体化一下:如果你编写了一个汽车赛车游戏,你很可能会使用一个表示汽车的对象。car
对象可能有一个speed
属性,通过car.speed
可以获取当前速度,并且你可以通过调用car.accelerate(10)
方法来加速汽车,这将使速度增加十英里每小时。
对象的类型及其行为由类定义,因此前面的例子需要你编写一个Car
类。从Car
类获取一个car
对象的过程称为实例化,你可以通过调用类来实例化对象,就像调用函数一样:car = Car()
。我们不会在本书中编写自己的类,但如果你对其工作原理感兴趣,请查看附录 C。
在接下来的一节中,我们将使用第一个对象方法使文本字符串变为大写,并且当我们讨论本章末尾关于datetime
对象时,我们将回到对象和类的主题。现在,让我们继续讨论具有数值数据类型的对象!
数值类型
数据类型int
和float
分别表示整数和浮点数。要找出给定对象的数据类型,使用内置的type
函数:
In``[``3``]:``type``(``4``)
Out[3]: int
In``[``4``]:``type``(``4.4``)
Out[4]: float
如果你想将数字强制转换为float
而不是int
,只需使用尾部的小数点或float
构造函数即可:
In``[``5``]:``type``(``4.``)
Out[5]: float
In``[``6``]:``float``(``4``)
Out[6]: 4.0
最后一个示例也可以反过来使用:使用 int
构造函数,你可以将 float
转换为 int
。如果小数部分不为零,它将被截断:
In``[``7``]:``int``(``4.9``)
Out[7]: 4
EXCEL 单元格始终存储浮点数
当你需要从 Excel 单元格中读取一个数,并将其作为整数传递给 Python 函数时,可能需要将
float
转换为int
。原因是,Excel 单元格中的数字始终以浮点数形式存储,即使 Excel 显示的是整数也是如此。
Python 还有一些数值类型,本书不会使用或讨论:有 decimal
、fraction
和 complex
数据类型。如果浮点数不准确是个问题(参见侧边栏),可以使用 decimal
类型获得精确的结果。不过,这类情况非常罕见。作为一个经验法则:如果 Excel 的计算结果已经足够,那就使用浮点数。
浮点数不准确
默认情况下,Excel 经常显示四舍五入的数字:在单元格中输入
=1.125-1.1
,你会看到0.025
。虽然这可能是你期望的结果,但这不是 Excel 内部存储的内容。将显示格式更改为至少显示 16 位小数,结果将变为0.0249999999999999
。这是浮点数不准确的影响:计算机生活在一个二进制世界中,即它们只能使用 0 和 1 进行计算。某些十进制分数如0.1
无法作为有限的二进制浮点数存储,这解释了减法的结果。在 Python 中,你会看到相同的效果,但 Python 不会隐藏小数部分:
In``[``8``]:``1.125``-``1.1
Out[8]: 0.02499999999999991
数学运算符
使用数值计算需要使用加号或减号等数学运算符。除了幂运算符外,如果你来自 Excel 的话,不应该有任何意外:
In``[``9``]:``3``+``4``# 求和
Out[9]: 7
In``[``10``]:``3``-``4``# 减法
Out[10]: -1
In``[``11``]:``3``/``4``# 除法
Out[11]: 0.75
In``[``12``]:``3``*``4``# 乘法
Out[12]: 12
In``[``13``]:``3``**``4``# 幂运算符(Excel 使用 3⁴)
Out[13]: 81
In``[``14``]:``3``*``(``3``+``4``)``# 使用括号
Out[14]: 21
注释
在前面的例子中,我用注释描述了示例的操作(例如
# 求和
)。注释有助于其他人(以及你在编写代码后的几周内)理解程序中的运行情况。最好的做法是只注释那些从代码阅读中不明显的事物:如果不确定,最好不要有注释,而不是有一个过时的注释与代码相矛盾。在 Python 中,任何以井号开头的内容都是注释,运行代码时会被忽略:
In``[``15``]:``# 这是我们之前见过的示例。``# 每行注释都必须以 # 开头``3``+``4
Out[15]: 7
In``[``16``]:``3``+``4``# 这是内联注释
Out[16]: 7
大多数编辑器都有快捷键可以注释或取消注释行。在 Jupyter 笔记本和 VS Code 中,Windows 下是 Ctrl+/,macOS 下是 Command-/。请注意,在 Jupyter 笔记本的 Markdown 单元格中不接受注释——如果您以
#
开头,Markdown 将把它解释为标题。
现在我们已经涵盖了整数和浮点数,让我们直接转到关于布尔值的下一节!
布尔值
Python 中的布尔类型为 True
或 False
,与 VBA 完全相同。然而,布尔运算符 and
、or
和 not
全部小写,而 VBA 则大写显示。布尔表达式类似于 Excel 中的工作方式,但等式和不等式运算符有所不同:
In``[``17``]:``3``==``4``# Equality (Excel uses 3 = 4)
Out[17]: False
In``[``18``]:``3``!=``4``# Inequality (Excel uses 3 <> 4)
Out[18]: True
In``[``19``]:``3``<``4``# Smaller than. Use > for bigger than.
Out[19]: True
In``[``20``]:``3``<=``4``# Smaller or equal. Use >= for bigger or equal.
Out[20]: True
In``[``21``]:``# You can chain logical expressions``# In VBA, this would be: 10 < 12 And 12 < 17``# In Excel formulas, this would be: =AND(10 < 12, 12 < 17)``10``<``12``<``17
Out[21]: True
In``[``22``]:``not``True``# "not" operator
Out[22]: False
In``[``23``]:``False``and``True``# "and" operator
Out[23]: False
In``[``24``]:``False``or``True``# "or" operator
Out[24]: True
每个 Python 对象都会评估为 True
或 False
。大多数对象为 True
,但有一些会评估为 False
,包括 None
(见侧边栏),False
,0
或空数据类型,例如空字符串(我将在下一节介绍字符串)。
NONE
None
是一个内置常量,代表“没有值”,根据官方文档。例如,如果函数没有显式返回任何内容,它将返回None
。它还是在 Excel 中表示空单元格的良好选择,我们将在 第 III 部分 和 第 IV 部分 中看到。
若要双重检查对象是否为 True
或 False
,请使用 bool
构造函数:
In``[``25``]:``bool``(``2``)
Out[25]: True
In``[``26``]:``bool``(``0``)
Out[26]: False
In``[``27``]:``bool``(``"some text"``)``# We'll get to strings in a moment
Out[27]: True
In``[``28``]:``bool``(``""``)
Out[28]: False
In``[``29``]:``bool``(``None``)
Out[29]: False
有了布尔值的支持,我们还剩下一种基本数据类型:文本数据,通常称为字符串。
字符串
如果你曾经在 VBA 中使用过超过一行的字符串,并且包含变量和文字引号,你可能希望它更加简单。幸运的是,Python 在这方面表现特别出色。字符串可以用双引号("
)或单引号('
)表示。唯一的条件是你必须用相同类型的引号开始和结束字符串。你可以使用 +
连接字符串或 *
重复字符串。既然我已经在前一章节的 Python REPL 中展示了重复的情况,这里是使用加号的示例:
In``[``30``]:``"一个双引号字符串。 "``+``'一个单引号字符串。'
Out[30]: '一个双引号字符串。一个单引号字符串。'
根据你想要写的内容,使用单引号或双引号可以帮助你轻松地打印文字引号,而不需要转义它们。如果你仍然需要转义字符,可以在其前面加上反斜杠:
In``[``31``]:``print``(``"不要等待! "``+``'学习如何 "说" Python。'``)
Don't wait! 学会如何 "说" Python。
In``[``32``]:``print``(``"It's easy to
\"``escape``\"
characters with a leading
\\``."``)
It's easy to "escape" characters with a leading \.
当你将字符串与变量混合使用时,通常使用格式化字符串字面量,即 f 字符串。只需在字符串前加上 f
,并在大括号中使用变量:
In``[``33``]:``# 请注意 Python 如何让你方便地在一行中为多个变量赋值``#``first_adjective``,``second_adjective``=``"free"``,``"开源的"
f``"Python 是{first_adjective}和{second_adjective}。"`
Out[33]: 'Python 是免费和开源的。'
正如我在本节开头提到的,字符串与其他一切一样,它们提供一些方法(即函数)来对字符串执行操作。例如,这是如何在大写和小写字母之间转换的示例:
In``[``34``]:``"PYTHON"``.``lower``()
Out[34]: 'python'
In``[``35``]:``"python"``.``upper``()
Out[35]: 'PYTHON'
获取帮助
你如何知道某些对象(如字符串)提供哪些属性以及它们的方法接受什么参数?答案有点依赖于你使用的工具:在 Jupyter 笔记本中,在输入对象后面的点后按 Tab 键,例如
"python".
<Tab>
。这将显示一个下拉菜单,其中包含此对象提供的所有属性和方法。如果你的光标在方法中,例如在"python".upper()
的括号内部,按 Shift+Tab 可以获取该函数的描述。VS Code 会自动显示此信息作为工具提示。如果在 Anaconda Prompt 上运行 Python REPL,使用dir("python")
获取可用属性,并使用help("python".upper)
打印upper
方法的描述。除此之外,随时查阅 Python 的在线文档总是一个好主意。如果你正在寻找像 pandas 这样的第三方包的文档,建议在PyPI上搜索它们,Python 的包索引,你将找到相应主页和文档的链接。
在处理字符串时,常见的任务是选择字符串的部分:例如,你可能想从EURUSD
的汇率符号中提取USD
部分。接下来的部分将展示 Python 强大的索引和切片机制,使你能够精确做到这一点。
索引和切片
索引和切片让你可以访问序列的特定元素。由于字符串是字符序列,我们可以利用它们来学习它是如何工作的。在接下来的部分中,我们将遇到额外的序列,如列表和元组,它们也支持索引和切片。
索引
图 3-1 介绍了索引的概念。Python 是从零开始索引的,这意味着序列中的第一个元素由索引0
引用。负索引从-1
允许你从序列的末尾引用元素。
图 3-1. 从序列的开始和结尾进行索引
VBA 开发人员的常见错误陷阱
如果你来自 VBA,索引是一个常见的错误陷阱。VBA 对于大多数集合(如
Sheets(1)
)使用基于一的索引,但对于数组(MyArray(0)
)使用基于零的索引,尽管该默认值可以更改。另一个不同之处在于,VBA 使用括号进行索引,而 Python 使用方括号。
索引的语法如下:
sequence``[``index``]
因此,你可以像这样访问字符串的特定元素:
In``[``36``]:``language``=``"PYTHON"
In``[``37``]:``language``[``0``]
Out[37]: 'P'
In``[``38``]:``language``[``1``]
Out[38]: 'Y'
In``[``39``]:``language``[``-``1``]
Out[39]: 'N'
In``[``40``]:``language``[``-``2``]
Out[40]: 'O'
你经常需要提取不止一个字符——这就是切片发挥作用的地方。
切片
如果你想从序列中获取多个元素,你可以使用切片语法,其工作方式如下:
sequence``[``start``:``stop``:``step``]
Python 使用半开区间:start
索引包含在内,stop
索引不包含在内。如果省略start
或stop
参数,它将分别包括从序列开始到结尾的所有内容。step
确定方向和步长:例如,2
将从左到右返回每第二个元素,-3
将从右到左返回每第三个元素。默认步长为一:
In``[``41``]:``language``[:``3``]``# 同
language[0:3]`
Out[41]: 'PYT'
In``[``42``]:``language``[``1``:``3``]
Out[42]: 'YT'
In``[``43``]:``language``[``-``3``:]``# 同
language[-3:6]`
Out[43]: 'HON'
In``[``44``]:``language``[``-``3``:``-``1``]
Out[44]: 'HO'
In``[``45``]:``language``[::``2``]``# 每第二个元素
Out[45]: 'PTO'
In``[``46``]:``language``[``-``1``:``-``4``:``-``1``]``# 负步长从右向左移动
Out[46]: 'NOH'
到目前为止,我们只看过单个索引或切片操作,但 Python 还允许您将多个索引和切片操作链接在一起。例如,如果要从最后三个字符中获取第二个字符,可以像这样做:
In``[``47``]:``language``[``-``3``:][``1``]
Out[47]: 'O'
这与language[-2]
相同,因此在这种情况下,使用链接并没有太多意义,但在下一节将介绍的列表中使用索引和切片将更有意义。
数据结构
Python 提供了强大的数据结构,使得处理对象集合非常容易。在本节中,我将介绍列表、字典、元组和集合。尽管这些数据结构各有些许不同的特性,但它们都能够容纳多个对象。在 VBA 中,您可能已经使用过集合或数组来保存多个值。VBA 甚至提供了一个名为字典的数据结构,其概念上与 Python 的字典相同。但是,它仅在 Excel 的 Windows 版本中默认可用。让我们从列表开始,这是您可能会经常使用的数据结构。
列表
列表能够容纳多个不同数据类型的对象。它们非常灵活,您会经常使用它们。您可以按以下方式创建列表:
[``element1``,``element2``,``...``]
这里有两个列表,一个是 Excel 文件名,另一个是一些数字:
In``[``48``]:``file_names``=``[``"one.xlsx"``,``"two.xlsx"``,``"three.xlsx"``]``numbers``=``[``1``,``2``,``3``]
像字符串一样,列表可以使用加号轻松连接。这也表明列表可以容纳不同类型的对象:
In``[``49``]:``file_names``+``numbers
Out[49]: ['one.xlsx', 'two.xlsx', 'three.xlsx', 1, 2, 3]
由于列表和其他所有东西一样都是对象,列表也可以将其他列表作为它们的元素。我将它们称为嵌套列表:
In``[``50``]:``nested_list``=``[[``1``,``2``,``3``],``[``4``,``5``,``6``],``[``7``,``8``,``9``]]
如果你重新排列它跨越多行,你可以轻松地识别出这是一个矩阵的非常好的表示,或者一个范围的电子表格单元格。请注意,方括号隐含允许你断开行(参见侧边栏)。通过索引和切片,你可以得到想要的元素:
In``[``51``]:``cells``=``[[``1``,``2``,``3``],``[``4``,``5``,``6``],``[``7``,``8``,``9``]]
In``[``52``]:``cells``[``1``]``# 第二行
Out[52]: [4, 5, 6]
In``[``53``]:``cells``[``1``][``1``:]``# 第二行,第二和第三列
Out[53]: [5, 6]
LINE CONTINUATION
有时,一行代码可能会变得如此之长,以至于你需要将其分成两行或更多行以保持代码可读性。从技术上讲,你可以使用括号或反斜杠来断开行:
In``[``54``]:``a``=``(``1``+``2``+``3``)
In``[``55``]:``a``=``1``+``2
\
+``3
然而,Python 的风格指南更喜欢你尽可能使用隐式换行:每当你使用包含括号、方括号或大括号的表达式时,使用它们来引入换行,而无需引入额外的字符。我将在本章末尾详细介绍 Python 的风格指南。
你可以更改列表中的元素:
In``[``56``]:``users``=``[``"Linda"``,``"Brian"``]
In``[``57``]:``users``.``append``(``"Jennifer"``)``# 通常在末尾添加``users
Out[57]: ['Linda', 'Brian', 'Jennifer']
In``[``58``]:``users``.``insert``(``0``,``"Kim"``)``# 在索引 0 处插入 "Kim"``users
Out[58]: ['Kim', 'Linda', 'Brian', 'Jennifer']
要删除一个元素,可以使用 pop
或 del
。虽然 pop
是一个方法,但 del
在 Python 中实现为语句:
In``[``59``]:``users``.``pop``()``# 默认移除并返回最后一个元素
Out[59]: 'Jennifer'
In``[``60``]:``users
Out[60]: ['Kim', 'Linda', 'Brian']
In``[``61``]:``del``users``[``0``]``# del 删除指定索引处的元素
其他一些你可以使用列表做的有用的事情是:
In``[``62``]:``len``(``users``)``# 长度
Out[62]: 2
In``[``63``]:``"Linda"``in``users``# 检查 users 是否包含 "Linda"
Out[63]: True
In``[``64``]:``print``(``sorted``(``users``))``# 返回一个新的排序列表``print``(``users``)``# 原始列表不变
['Brian', 'Linda'] ['Linda', 'Brian']
In``[``65``]:``users``.``sort``()``# 对原始列表进行排序``users
Out[65]: ['Brian', 'Linda']
请注意,你也可以在字符串中使用 len
和 in
:
In``[``66``]:``len``(``"Python"``)
Out[66]: 6
In``[``67``]:``"free"``in``"Python is free and open source."
Out[67]: True
要访问列表中的元素,你可以按它们的位置或索引引用它们——这并不总是实际可行的。下一节将讨论的字典允许你通过键(通常是名称)访问元素。
字典
字典将键映射到值。您将经常遇到键/值组合。创建字典的最简单方法如下:
{``key1``:``value1``,``key2``:``value2``,``...``}
虽然列表允许您按索引,即位置,访问元素,但字典允许您按键访问元素。与索引一样,键是通过方括号访问的。以下代码示例将使用货币对(键)映射到汇率(值):
In``[``68``]:``exchange_rates``=``{``"EURUSD"``:``1.1152``,``"GBPUSD"``:``1.2454``,``"AUDUSD"``:``0.6161``}
In``[``69``]:``exchange_rates``[``"EURUSD"``]``# 访问 EURUSD 汇率
Out[69]: 1.1152
下面的示例向您展示如何更改现有值并添加新的键值对:
In``[``70``]:``exchange_rates``[``"EURUSD"``]``=``1.2``# 更改现有值``exchange_rates
Out[70]: {'EURUSD': 1.2, 'GBPUSD': 1.2454, 'AUDUSD': 0.6161}
In``[``71``]:``exchange_rates``[``"CADUSD"``]``=``0.714``# 添加一个新的键值对``exchange_rates
Out[71]: {'EURUSD': 1.2, 'GBPUSD': 1.2454, 'AUDUSD': 0.6161, 'CADUSD': 0.714}
合并两个或更多字典的最简单方法是将它们解压到一个新字典中。您通过使用两个前导星号来解压字典。如果第二个字典包含来自第一个字典的键,则将从第一个字典中覆盖值。您可以通过查看GBPUSD
汇率来看到这种情况发生:
In``[``72``]:``{``**``exchange_rates``,``**``{``"SGDUSD"``:``0.7004``,``"GBPUSD"``:``1.2222``}}
Out[72]: {'EURUSD': 1.2, 'GBPUSD': 1.2222, 'AUDUSD': 0.6161, 'CADUSD': 0.714, 'SGDUSD': 0.7004}
Python 3.9 引入了管道字符作为字典的专用合并操作符,这使得您可以简化先前的表达式为:
exchange_rates``|``{``"SGDUSD"``:``0.7004``,``"GBPUSD"``:``1.2222``}
许多对象都可以作为键;以下是一个带整数的示例:
In``[``73``]:``currencies``=``{``1``:``"EUR"``,``2``:``"USD"``,``3``:``"AUD"``}
In``[``74``]:``currencies``[``1``]
Out[74]: 'EUR'
通过使用get
方法,字典允许您在键不存在的情况下使用默认值:
In``[``75``]:``# currencies[100]会引发异常。您可以使用任何其他不存在的键。``# 代替 100,也是一样的。``currencies``.``get``(``100``,``"N/A"``)
Out[75]: 'N/A'
在 VBA 中,字典通常可以用作Case
语句的替代。前面的示例可以在 VBA 中这样写:
Select``Case``x``Case``1``Debug``.``Print``"EUR"``Case``2``Debug``.``Print``"USD"``Case``3``Debug``.``Print``"AUD"``Case``Else``Debug``.``Print``"N/A"``End``Select
现在您知道如何使用字典了,让我们继续下一个数据结构:元组。它们与列表类似,但有一个重大区别,我们将在下一节中看到。
元组
元组与列表类似,区别在于它们是不可变的:一旦创建,它们的元素就不能更改。虽然您通常可以互换使用元组和列表,但元组是在整个程序中从不更改的集合的明显选择。通过用逗号分隔值来创建元组:
mytuple``=``element1``,``element2``,``...
使用括号通常使其更易读:
In``[``76``]:``currencies``=``(``"EUR"``,``"GBP"``,``"AUD"``)
元组允许您以与列表相同的方式访问元素,但不允许更改元素。相反,连接元组将在幕后创建一个新元组,然后将您的变量绑定到此新元组:
In``[``77``]:``currencies``[``0``]``# 访问第一个元素
Out[77]: 'EUR'
In``[``78``]:``# 连接元组将返回一个新的元组。``currencies``+``(``"SGD"``,)
Out[78]: ('EUR', 'GBP', 'AUD', 'SGD')
我在附录 C 中详细解释了可变对象与不可变对象的区别,但现在让我们来看看本节的最后一个数据结构:集合。
集合
集合是没有重复元素的集合。虽然您可以用它们进行集合理论运算,在实践中它们经常帮助您获取列表或元组的唯一值。通过使用大括号来创建集合:
{``element1``,``element2``,``...``}
要获取列表或元组中的唯一对象,请使用set
构造函数,如下所示:
In``[``79``]:``set``([``"USD"``,``"USD"``,``"SGD"``,``"EUR"``,``"USD"``,``"EUR"``])
Out[79]: {'EUR', 'SGD', 'USD'}
除此之外,您还可以应用集合理论操作,如交集和并集:
In``[``80``]:``portfolio1``=``{``"USD"``,``"EUR"``,``"SGD"``,``"CHF"``}``portfolio2``=``{``"EUR"``,``"SGD"``,``"CAD"``}
In``[``81``]:``# 等同于 portfolio2.union(portfolio1)``portfolio1``.``union``(``portfolio2``)
Out[81]: {'CAD', 'CHF', 'EUR', 'SGD', 'USD'}
In``[``82``]:``# 等同于 portfolio2.intersection(portfolio1)``portfolio1``.``intersection``(``portfolio2``)
Out[82]: {'EUR', 'SGD'}
要获取有关集合操作的完整概述,请参阅官方文档。在继续之前,让我们快速复习我们刚刚在表 3-1 中见过的四种数据结构。它展示了每种数据结构的一个示例,采用了我在前面段落中使用的符号,即所谓的字面量。此外,我还列出了它们的构造函数,这些构造函数提供了一种将一种数据结构转换为另一种的替代方法。例如,要将元组转换为列表,请执行以下操作:
In``[``83``]:``currencies``=``"USD"``,``"EUR"``,``"CHF"``currencies
Out[83]: ('USD', 'EUR', 'CHF')
In``[``84``]:``list``(``currencies``)
Out[84]: ['USD', 'EUR', 'CHF']
表格 3-1. 数据结构
数据结构 | 字面量 | 构造函数 |
---|---|---|
列表 | [1, 2, 3] |
list((1, 2, 3)) |
字典 | {"a": 1, "b": 2} |
dict(a=1, b=2) |
Tuple | (1, 2, 3) |
tuple([1, 2, 3]) |
Set | {1, 2, 3} |
set((1, 2, 3)) |
到目前为止,你已经了解了所有重要的数据类型,包括基本类型如浮点数和字符串,以及数据结构如列表和字典。在下一节中,我们将继续学习控制流。
控制流
本节将介绍if
语句以及for
和while
循环。if
语句允许你仅在满足条件时执行某些代码行,而for
和while
循环将重复执行一段代码。在本节末尾,我还将介绍列表推导式,这是构建列表的一种方式,可以作为for
循环的替代方式。我将从代码块的定义开始本节,这也是需要介绍 Python 最显著的特殊之一:显著的空白。
代码块和pass
语句
代码块定义了你源代码中用于特殊用途的部分。例如,你可以使用代码块来定义程序循环的行或者它构成函数的定义。在 Python 中,你通过缩进而不是使用关键字(如 VBA 中)或者花括号(大多数其他语言中)来定义代码块。这被称为显著的空白。Python 社区已经约定使用四个空格作为缩进,但你通常会通过按 Tab 键来输入它们:Jupyter 笔记本和 VS Code 会自动将你的 Tab 键转换为四个空格。让我演示一下如何使用if
语句来正式定义代码块:
if``condition``:``pass``# 什么都不做
代码块前面的行总是以冒号结束。当你不再缩进行时,代码块结束。如果你想创建一个什么都不做的虚拟代码块,你需要使用pass
语句。在 VBA 中,这对应以下内容:
If``condition``Then``' 什么都不做``End``If
现在你知道如何定义代码块了,让我们在下一节开始使用它们,我将在那里适当地介绍if
语句。
if
语句和条件表达式
要介绍if
语句,让我来重现“可读性和可维护性”中的示例,在第一章中,但这次是用 Python:
In``[``85``]:``i``=``20``if``i``<``5``:``print``(``"i is smaller than 5"``)``elif``i``<=``10``:``print``(``"i is between 5 and 10"``)``else``:``print``(``"i is bigger than 10"``)
i is bigger than 10
如果你想做和我们在第一章里做的一样,即缩进elif
和else
语句,你会得到SyntaxError
。Python 不允许你的代码缩进与逻辑不同。与 VBA 不同的是,Python 的关键词是小写的,而不是 VBA 中的ElseIf
,Python 使用elif
。if
语句是判断一个程序员是否新手或者已经采用 Python 风格的简单方法:在 Python 中,一个简单的if
语句不需要括号,而测试一个值是否为True
,你不需要显式地这样做。这就是我的意思:
In``[``86``]:``is_important``=``True``if``is_important``:``print``(``"这很重要。"``)``else``:``print``(``"这不重要。"``)
这很重要。
要检查类似列表这样的序列是否为空,使用相同的方法:
In``[``87``]:``values``=``[]``如果``values``:``print``(``f``"提供了以下数值:{values}"``)``else``:``print``(``"未提供数值。"``)
未提供数值。
来自其他语言的程序员通常会写类似if (is_important == True)
或者if len(values) > 0
的代码。
条件表达式,也称为三元运算符,允许你在简单的if
/else
语句中使用更紧凑的风格:
In``[``88``]:``is_important``=``False``print``(``"重要"``)``if``is_important``else``print``(``"不重要"``)
不重要
有了if
语句和条件表达式,让我们在下一节转向for
和while
循环。
for
和while
循环
如果你需要重复做一些像打印十个不同变量的值这样的事情,最好不要把打印语句复制/粘贴十次。而是使用for
循环为你做这些工作。for
循环遍历序列的项目,比如列表、元组或者字符串(记住,字符串是字符序列)。作为一个介绍性的例子,让我们创建一个for
循环,它将currencies
列表的每个元素赋值给变量currency
,然后逐个打印出来,直到列表中没有更多的元素:
In``[``89``]:``currencies``=``[``"USD"``,``"HKD"``,``"AUD"``]``for``currency``in``currencies``:``print``(``currency``)
USD HKD AUD
顺便提一下,VBA 的For Each
语句与 Python 的for
循环工作方式相似。前面的例子在 VBA 中可以这样写:
Dim``currencies``As``Variant``Dim``curr``As``Variant``'currency is a reserved word in VBA``currencies``=``Array``(``"USD"``,``"HKD"``,``"AUD"``)
For``Each``curr``In``currencies``Debug``.``Print``curr``Next
在 Python 中,如果你需要在 for
循环中一个计数变量,range
或 enumerate
内置函数可以帮助你。让我们先看看 range
,它提供一系列数字:你可以通过提供单个 stop
参数或提供 start
和 stop
参数(带有可选的 step
参数)来调用它。与切片一样,start
包含在内,stop
是排外的,step
决定步长,其中 1
是默认值:
range``(``stop``)``range``(``start``,``stop``,``step``)
range
评估是懒惰的,这意味着如果不明确要求,你看不到它生成的序列:
In``[``90``]:``range``(``5``)
Out[90]: range(0, 5)
将 range 转换为列表可以解决这个问题:
In``[``91``]:``list``(``range``(``5``))``# stop argument
Out[91]: [0, 1, 2, 3, 4]
In``[``92``]:``list``(``range``(``2``,``5``,``2``))``# start, stop, step arguments
Out[92]: [2, 4]
大多数情况下,不需要用 list
包装 range
:
In``[``93``]:``for``i``in``range``(``3``):``print``(``i``)
0 1 2
如果你在遍历序列时需要一个计数器变量,请使用 enumerate
。它返回一个 (索引, 元素)
元组序列。默认情况下,索引从零开始,逐一递增。你可以像这样在循环中使用 enumerate
:
In``[``94``]:``for``i``,``currency``in``enumerate``(``currencies``):``print``(``i``,``currency``)
0 美元 1 港币 2 澳大利亚元
遍历元组和集合的方式与列表相同。当你遍历字典时,Python 会遍历键:
In``[``95``]:``exchange_rates``=``{``"EURUSD"``:``1.1152``,``"GBPUSD"``:``1.2454``,``"AUDUSD"``:``0.6161``}``for``currency_pair``in``exchange_rates``:``print``(``currency_pair``)
EURUSD GBPUSD AUDUSD
使用 items
方法,你可以同时获取键和值作为元组:
In``[``96``]:``for``currency_pair``,``exchange_rate``in``exchange_rates``.``items``():``print``(``currency_pair``,``exchange_rate``)
EURUSD 1.1152 GBPUSD 1.2454 AUDUSD 0.6161
要退出循环,使用 break
语句:
In``[``97``]:``for``i``in``range``(``15``):``if``i``==``2``:``break``else``:``print``(``i``)
0 1
你可以使用 continue
语句跳过循环的剩余部分,这意味着执行将继续新的循环和下一个元素:
In``[``98``]:``for``i``in``range``(``4``):``if``i``==``2``:``continue``else``:``print``(``i``)
0 1 3
在比较 VBA 中的 for 循环与 Python 时,有一个微妙的区别:在 VBA 中,计数器变量在完成循环后会超出你的上限:
For``i``=``1``To``3``Debug``.``Print``i``Next``i``Debug``.``Print``i
这会打印:
1 2 3 4
在 Python 中,它的行为就像你可能期望的那样:
In``[``99``]:``for``i``in``range``(``1``,``4``):``print``(``i``)``print``(``i``)
1 2 3 3
要遍历序列而不是使用 for
循环,你也可以使用 while 循环在满足某个条件时运行循环:
In``[``100``]:``n``=``0``while``n``<=``2``:``print``(``n``)``n``+=``1
0 1 2
增强赋值
在上一个例子中,我使用了增强赋值符号:
n += 1
。这与您写n = n + 1
是一样的。它也适用于我之前介绍的所有其他数学运算符;例如,减法您可以写n -= 1
。
经常需要收集列表中的某些元素进行进一步处理。在这种情况下,Python 提供了一种替代循环的方式:列表、字典和集合推导式。
列表、字典和集合推导式
列表、字典和集合推导式技术上是创建相应数据结构的一种方式,但它们经常替代for
循环,这就是我在这里介绍它们的原因。假设在以下 USD 货币对的列表中,您想挑选出 USD 作为第二个货币报价的那些货币。您可以写以下for
循环:
In``[``101``]:``currency_pairs``=``[``"USDJPY"``,``"USDGBP"``,``"USDCHF"``,``"USDCAD"``,``"AUDUSD"``,``"NZDUSD"``]
In``[``102``]:``usd_quote``=``[]``for``pair``in``currency_pairs``:``if``pair``[``3``:]``==``"USD"``:``usd_quote``.``append``(``pair``[:``3``])``usd_quote
Out[102]: ['AUD', 'NZD']
使用列表推导式通常更容易编写。列表推导式是创建列表的简洁方式。您可以从这个示例中获取其语法,它与前面的for
循环做的事情是一样的:
In``[``103``]:``[``pair``[:``3``]``for``pair``in``currency_pairs``if``pair``[``3``:]``==``"USD"``]
Out[103]: ['AUD', 'NZD']
如果没有任何条件需要满足,可以简单地省略if
部分。例如,要颠倒所有货币对,使第一个货币变成第二个货币,反之亦然,可以这样做:
In``[``104``]:``[``pair``[``3``:]``+``pair``[:``3``]``for``pair``in``currency_pairs``]
Out[104]: ['JPYUSD', 'GBPUSD', 'CHFUSD', 'CADUSD', 'USDAUD', 'USDNZD']
对于字典,还有字典推导式:
In``[``105``]:``exchange_rates``=``{``"EURUSD"``:``1.1152``,``"GBPUSD"``:``1.2454``,``"AUDUSD"``:``0.6161``}``{``k``:``v``*``100``for``(``k``,``v``)``in``exchange_rates``.``items``()}
Out[105]: {'EURUSD': 111.52, 'GBPUSD': 124.54, 'AUDUSD': 61.61}
而对于集合,有集合推导式:
In``[``106``]:``{``s``+``"USD"``for``s``in``[``"EUR"``,``"GBP"``,``"EUR"``,``"HKD"``,``"HKD"``]}
Out[106]: {'EURUSD', 'GBPUSD', 'HKDUSD'}
到目前为止,您已经能够编写简单的脚本,因为您已经了解了大多数 Python 的基本构建块。在下一节中,当您的脚本开始变得更大时,您将学习如何组织您的代码以保持可维护性。
代码组织
在本节中,我们将了解如何将代码组织成可维护的结构:我将首先介绍你通常需要的所有函数细节,然后展示如何将代码分割成不同的 Python 模块。关于模块的知识将使我们能够在本节结束时研究标准库中的datetime
模块。
函数
即使你只会在 Python 中写简单脚本,你仍然会经常编写函数:它们是每种编程语言中最重要的结构之一,允许你从程序的任何地方重用相同的代码行。在我们看如何调用它之前,我们将在本节开始时定义一个函数!
定义函数
要在 Python 中编写自己的函数,你必须使用关键字def
,它代表函数定义。与 VBA 不同,Python 不区分函数和子程序。在 Python 中,子程序的等价物就是一个不返回任何东西的函数。Python 中的函数遵循代码块的语法,即你用冒号结束第一行,然后缩进函数体:
def``function_name``(``required_argument``,``optional_argument``=``default_value``,``...``):``return``value1``,``value2``,``...
必需参数
必需参数没有默认值。多个参数用逗号分隔。
可选参数
通过提供默认值来使参数可选。如果没有有意义的默认值,通常使用
None
来使参数可选。
返回值
return
语句定义函数返回的值。如果省略它,函数将自动返回None
。Python 方便地允许你返回多个用逗号分隔的值。
为了能够操作一个函数,让我们定义一个能够将温度从华氏度或开尔文转换为摄氏度的函数:
In``[``107``]:``def``convert_to_celsius``(``degrees``,``source``=``"fahrenheit"``):``if``source``.``lower``()``==``"fahrenheit"``:``return``(``degrees``-``32``)``*``(``5``/``9``)``elif``source``.``lower``()``==``"kelvin"``:``return``degrees``-``273.15``else``:``return``f``"不知道如何从 {source} 转换"
我使用了字符串方法lower
,它将提供的字符串转换为小写。这允许我们接受带有任何大小写的source
字符串,而比较仍然有效。有了convert_to_celsius
函数的定义,让我们看看如何调用它!
调用函数
正如本章开头简要提到的,通过在函数名后面添加括号并包围函数参数来调用函数:
value1``,``value2``,``...``=``function_name``(``positional_arg``,``arg_name``=``value``,``...``)
位置参数
如果你把一个值作为位置参数(
positional_arg
)提供,这些值将根据它们在函数定义中的位置进行匹配。
关键字参数
通过以
arg_name=value
的形式提供参数,您提供了一个关键字参数。这样做的好处是可以任意顺序提供参数。对于读者来说更加明确,有助于理解。例如,如果函数定义为f(a, b)
,您可以像这样调用函数:f(b=1, a=2)
。这个概念在 VBA 中也存在,您可以通过像这样调用函数来使用关键字参数:f(b:=1, a:=1)
。
让我们玩转convert_to_celsius
函数,看看实际操作中的运作方式:
In``[``108``]:``convert_to_celsius``(``100``,``"fahrenheit"``)``# 位置参数
Out[108]: 37.77777777777778
In``[``109``]:``convert_to_celsius``(``50``)``# 将使用默认来源(fahrenheit)
Out[109]: 10.0
In``[``110``]:``convert_to_celsius``(``source``=``"kelvin"``,``degrees``=``0``)``# 关键字参数
Out[110]: -273.15
现在您已经知道如何定义和调用函数,让我们看看如何借助模块组织它们。
模块和导入语句
当您为更大的项目编写代码时,最终必须将其拆分为不同的文件,以便能够将其组织成可维护的结构。正如我们在前一章中已经看到的,Python 文件的扩展名为.py
,通常将主文件称为脚本。如果您现在希望您的主脚本能够访问其他文件中的功能,首先需要导入该功能。在此上下文中,Python 源文件称为模块。要更好地了解其工作原理以及不同的导入选项,请查看伴随存储库中的文件 temperature.py,并使用 VS Code 打开它(示例 3-1)。如果您需要再次了解如何在 VS Code 中打开文件,请再次查看第二章。
示例 3-1. temperature.py
TEMPERATURE_SCALES``=``(``"fahrenheit"``,``"kelvin"``,``"celsius"``)``def``convert_to_celsius``(``degrees``,``source``=``"fahrenheit"``):``if``source``.``lower``()``==``"fahrenheit"``:``return``(``degrees``-``32``)``*``(``5``/``9``)``elif``source``.``lower``()``==``"kelvin"``:``return``degrees``-``273.15``else``:``return``f``"无法从 {source} 转换"``print``(``"这是温度模块。"``)
要能够从您的 Jupyter 笔记本中导入temperature
模块,您需要确保 Jupyter 笔记本和temperature
模块在同一个目录中——就像伴随存储库的情况一样。要导入,只需使用模块的名称,不需要.py
扩展名。运行import
语句后,您将能够通过点符号访问该 Python 模块中的所有对象。例如,使用temperature.convert_to_celsius()
执行您的转换操作:
`In
[
111]:
import``temperature
这是温度模块。
In``[``112``]:``temperature``.``TEMPERATURE_SCALES
Out[112]: ('fahrenheit', 'kelvin', 'celsius')
In``[``113``]:
temperature``.``convert_to_celsius``(``120``,``"fahrenheit"``)
Out[113]:
48.88888888888889
请注意,我在TEMPERATURE_SCALES
中使用了大写字母来表示它是一个常量——我将在本章末尾进一步讨论这一点。当你执行带有import temperature
的单元格时,Python 将从上到下运行 temperature.py 文件。你可以很容易地看到这一点,因为导入模块将触发 temperature.py 底部的打印函数。
模块只被导入一次
如果你再次运行
import temperature
单元格,你会注意到它不再打印任何东西。这是因为 Python 模块在会话中只被导入一次。如果你更改了导入模块中的代码,你需要重新启动 Python 解释器才能应用所有更改,即在 Jupyter 笔记本中,你需要点击 Kernel > Restart。
实际上,通常你不会在模块中打印任何东西。这只是为了向你展示多次导入模块的效果。通常情况下,你会在模块中放置函数和类(有关类的更多信息,请参见附录 C)。如果你不想每次使用temperature
模块中的对象时都输入temperature
,可以像这样更改import
语句:
In``[``114``]:
import``temperature``as``tp
In``[``115``]:
tp``.``TEMPERATURE_SCALES
Out[115]:
(fahrenheit
,kelvin
,celsius
)`
给你的模块分配一个短的别名tp
可以使其在使用时更容易,但仍然清楚对象来自哪里。许多第三方包在使用别名时建议使用特定的约定。例如,pandas 使用import pandas as pd
。还有一种从另一个模块导入对象的选项:
In``[``116``]:
from``temperature``import``TEMPERATURE_SCALES
,``convert_to_celsius`
In``[``117``]:
TEMPERATURE_SCALES
Out[117]:
(fahrenheit
,kelvin
,celsius
)`
__PYCACHE__
文件夹当你导入
temperature
模块时,你会发现 Python 创建了一个名为__pycache__
的文件夹,其中的文件扩展名为.pyc
。这些是 Python 解释器在导入模块时创建的字节编译文件。对于我们的目的,我们可以简单地忽略这个文件夹,因为这是 Python 运行代码的技术细节。
当使用from x import y
语法时,你仅导入特定的对象。通过这样做,你将它们直接导入到你主脚本的命名空间中:也就是说,如果没有查看import
语句,你无法知道导入的对象是在你当前的 Python 脚本还是 Jupyter 笔记本中定义的,还是来自另一个模块。这可能会导致冲突:如果你的主脚本有一个名为convert_to_celsius
的函数,它会覆盖从temperature
模块导入的函数。然而,如果你使用前面两种方法之一,你的本地函数和导入模块中的函数可以像convert_to_celsius
和temperature.convert_to_celsius
这样共存。
不要将脚本命名为现有的包名
命名 Python 文件与现有的 Python 包或模块相同是常见的错误源。如果你创建一个文件来测试一些 pandas 功能,请不要将该文件命名为 pandas.py,因为这可能会导致冲突。
现在你了解了导入机制的工作原理,让我们立即使用它来导入datetime
模块!这也将使你能够学习关于对象和类的一些更多内容。
datetime 类
在 Excel 中,处理日期和时间是一种常见的操作,但它带有一些限制:例如,Excel 的时间单元格格式不支持比毫秒更小的单位,时间区域也完全不支持。在 Excel 中,日期和时间存储为称为日期序列号的简单浮点数。然后,Excel 单元格被格式化为显示日期和/或时间。例如,1900 年 1 月 1 日的日期序列号为 1,这意味着这也是你可以在 Excel 中使用的最早日期。时间被转换为浮点数的小数部分,例如,01/01/1900 10:10:00
被表示为1.4236111111
。
在 Python 中,要处理日期和时间,你需要导入标准库中的datetime
模块。datetime
模块包含同名的类,允许我们创建datetime
对象。由于模块和类的名称相同可能会导致混淆,我将在本书中使用以下导入约定:import datetime as dt
。这样可以很容易区分模块(dt
)和类(datetime
)。
到目前为止,我们大部分时间都在使用字面值来创建列表或字典等对象。字面值是指 Python 识别为特定对象类型的语法—例如列表的情况下,这可能是像[1, 2, 3]
这样的东西。然而,大多数对象必须通过调用它们的类来创建:这个过程称为实例化,因此对象也称为类实例。调用一个类的方式与调用函数的方式相同,即你需要在类名后加上括号,并以与我们在函数中所做的方式相同的方式提供参数。要实例化一个datetime
对象,你需要像这样调用类:
import``datetime``as``dt``dt``.``datetime``(``year``,``month``,``day``,``hour``,``minute``,``second``,``microsecond``,``timezone``)
让我们通过几个例子看看如何在 Python 中处理 datetime
对象。为了本次介绍的目的,让我们忽略时区并使用没有时区信息的 datetime
对象工作:
In``[``118``]:``# 将 datetime 模块导入为 "dt"``import``datetime``as``dt
In``[``119``]:``# 实例化一个名为 "timestamp" 的 datetime 对象``timestamp``=``dt``.``datetime``(``2020``,``1``,``31``,``14``,``30``)``timestamp
Out[119]: datetime.datetime(2020, 1, 31, 14, 30)
In``[``120``]:``# Datetime objects offer various attributes, e.g., to get the day``timestamp``.``day
Out[120]: 31
In``[``121``]:``# 两个 datetime 对象的差返回一个 timedelta 对象``timestamp``-``dt``.``datetime``(``2020``,``1``,``14``,``12``,``0``)
Out[121]: datetime.timedelta(days=17, seconds=9000)
In``[``122``]:``# 相应地,您还可以使用 timedelta 对象``timestamp``+``dt``.``timedelta``(``days``=``1``,``hours``=``4``,``minutes``=``11``)
Out[122]: datetime.datetime(2020, 2, 1, 18, 41)
要将 datetime
对象格式化为字符串,请使用 strftime
方法;要解析字符串并将其转换为 datetime
对象,请使用 strptime
函数(您可以在 datetime 文档 中找到接受的格式代码的概述):
In``[``123``]:``# 以特定方式格式化 datetime 对象``# 您也可以使用 f-string:f"{timestamp:%d/%m/%Y %H:%M}"``timestamp``.``strftime``(``"``%d``/``%m``/``%Y``%H``:``%M``"``)
Out[123]: '31/01/2020 14:30'
In``[``124``]:``# 将字符串解析为 datetime 对象``dt``.``datetime``.``strptime``(``"12.1.2020"``,``"``%d``.``%m``.``%Y``"``)
Out[124]: datetime.datetime(2020, 1, 12, 0, 0)
在这个对 datetime
模块的简短介绍之后,让我们继续本章的最后一个主题,即如何正确格式化代码。
PEP 8:Python 代码风格指南
也许你一直在想,为什么有时我会用带有下划线或全大写的变量名。这一节将通过介绍 Python 的官方风格指南来解释我的格式选择。Python 使用所谓的 Python Enhancement Proposals(PEP)来讨论引入新语言特性。其中之一,Python 代码风格指南通常用其编号来指代:PEP 8. PEP 8 是 Python 社区的一套风格建议;如果所有在同一代码上工作的人都遵循相同的风格指南,代码会变得更加可读。在开源世界尤为重要,因为许多程序员在同一项目上工作,通常彼此并不认识。Example 3-2 展示了一个简短的 Python 文件,介绍了最重要的惯例。
Example 3-2. pep8_sample.py
"""这个脚本展示了一些 PEP 8 规则。
"""``import``datetime``as``dt
TEMPERATURE_SCALES``=``(``"fahrenheit"``,``"kelvin"``,``"celsius"``)
class``TemperatureConverter``:
pass``# 目前什么也不做
def``convert_to_celsius``(``degrees``,``source``=``"fahrenheit"``):
"""这个函数将华氏度或开尔文转换为摄氏度。
"""``if``source``.``lower``()``==``"fahrenheit"``:
return``(``degrees``-``32``)``*``(``5``/``9``)
elif``source``.``lower``()``==``"kelvin"``:``return``degrees``-``273.15``else``:``return``f``"不知道如何从 {source} 转换"
celsius=
convert_to_celsius(
44,
source=
"fahrenheit")``non_celsius_scales
=TEMPERATURE_SCALES
[:``-``1``]
print``(``"当前时间: "``+``dt``.``datetime``.``now``()``.``isoformat``())``print``(``f``"摄氏度温度为: {celsius}"``)`
使用顶部的文档字符串解释脚本/模块的功能。文档字符串是一种特殊类型的字符串,用三个引号括起来。除了作为代码文档的字符串外,文档字符串还使得可以轻松地编写多行字符串,特别是在文本包含大量双引号或单引号时,不需要转义它们。如果您的文本包含大量的多行 SQL 查询,文档字符串非常有用,正如我们将在 第十一章 中看到的。
所有导入语句都位于文件顶部,每行一个。首先列出标准库的导入,然后是第三方包的导入,最后是自己模块的导入。本示例仅使用标准库。
使用大写字母和下划线表示常量。每行最大长度为 79 个字符。如果可能,利用括号、方括号或大括号进行隐式换行。
将类和函数与代码其余部分用两个空行分开。
尽管像
datetime
这样的许多类都是小写的,但您自己的类应使用CapitalizedWords
作为名称。有关类的更多信息,请参阅 附录 C。
行内注释应与代码至少用两个空格分开。代码块应缩进四个空格。
函数和函数参数应使用小写名称,并在提高可读性时使用下划线。不要在参数名和其默认值之间使用空格。
函数的文档字符串还应列出并解释函数参数。为了使示例简短,我没有在此处执行此操作,但在伴随存储库中包含的 excel.py 文件中,您将找到完整的文档字符串,并且我们将在第八章中遇到它。
在冒号周围不要使用空格。
在数学运算符周围使用空格。如果使用不同优先级的运算符,可以考虑只在优先级最低的运算符周围添加空格。因为本例中的乘法优先级最低,所以我已经在其周围添加了空格。
对变量使用小写名称。如果使用下划线可以提高可读性,则使用下划线。在赋值变量名时,使用等号周围的空格。但是,在调用带有关键字参数的函数时,不要在等号周围使用空格。
在索引和切片时,不要在方括号周围使用空格。
这是对 PEP 8 的简化总结,因此当你开始更认真地学习 Python 时,建议查看原始的 PEP 8。PEP 8 明确指出它是一种推荐,并且你自己的风格指南将优先。毕竟,一致性是最重要的因素。如果你对其他公开可用的指南感兴趣,可以看一下 Google 的 Python 风格指南,它与 PEP 8 非常接近。在实践中,大多数 Python 程序员宽松遵守 PEP 8,而忽略最大行长度 79 字符可能是最常见的错误。
由于在编写代码时可能难以正确格式化代码,因此可以自动检查代码风格。下一节将向您展示如何在 VS Code 中进行此操作。
PEP 8 和 VS Code
当使用 VS Code 时,确保你的代码符合 PEP 8 的一种简单方法是使用代码检查工具(linter)。代码检查工具会检查你的源代码是否存在语法和风格错误。在命令面板中启动(Windows 上为 Ctrl+Shift+P,macOS 上为 Command-Shift-P),搜索 Python: Select Linter。一个常用的选项是 flake8,这是 Anaconda 预装的一个包。如果启用了代码检查工具,在保存文件时,VS Code 会用波浪线下划线标出问题。将鼠标悬停在波浪线下划线上时,会显示工具提示来解释问题。你可以通过在命令面板中搜索 “Python: Enable Linting”,选择 “Disable Linting” 来关闭代码检查工具。如果你愿意,也可以在 Anaconda Prompt 上运行 flake8
命令以获取打印的报告(该命令仅在违反 PEP 8 规范时才会打印输出,因此在 pep8_sample.py 上运行时除非引入违规,否则不会打印任何内容):
(base)>
cd C:\Users\``username``\python-for-excel
(base)>
flake8 pep8_sample.py
Python 最近通过添加对类型提示的支持,将静态代码分析推进了一步。接下来的部分将解释它们是如何工作的。
类型提示
在 VBA 中,你经常会看到每个变量都用数据类型的缩写作为前缀,比如strEmployeeName
或wbWorkbookName
。虽然在 Python 中没有人会阻止你这样做,但这并不常见。你也不会找到类似于 VBA 的Option Explicit
或Dim
语句来声明变量的类型。相反,Python 3.5 引入了一种称为类型提示的功能。类型提示也被称为类型注解,允许你声明变量的数据类型。它们是完全可选的,并不影响 Python 解释器运行代码的方式(不过,有第三方包如pydantic可以在运行时强制执行类型提示)。类型提示的主要目的是允许像 VS Code 这样的文本编辑器在运行代码之前捕捉更多的错误,而且还可以改善 VS Code 和其他编辑器的代码自动完成功能。用于类型注解代码的最流行类型检查器是 mypy,它作为 VS Code 的一个代码检查工具提供。为了感受类型注解在 Python 中的工作原理,这里有一个短小的示例,没有类型提示:
x``=``1``def``hello``(``name``):``return``f``"Hello {name}!"
再次来看类型提示:
x``:``int``=``1``def``hello``(``name``:``str``)``->``str``:``return``f``"Hello {name}!"
类型提示通常在更大的代码库中更有意义,因此在本书的剩余部分我将不会使用它们。
结论
本章是对 Python 的一个紧凑介绍。我们了解了语言的最重要的构建块,包括数据结构、函数和模块。我们还触及了 Python 的一些特殊之处,如有意义的空白和代码格式化准则,更为人熟知的是 PEP 8。作为初学者,为了继续学习这本书,你不需要了解所有细节:只需了解列表和字典、索引和切片,以及如何使用函数、模块、for
循环和if
语句就可以走得很远。
与 VBA 相比,我发现 Python 更一致和更强大,但同时也更容易学习。如果你是 VBA 的铁杆粉丝,并且这一章还没能说服你,那么下一部分肯定会:在那里,我将为你介绍基于数组的计算,在我们开始使用 pandas 库进行数据分析之前。让我们通过学习关于 NumPy 的一些基础知识来开始第二部分!
第二部分:pandas 入门
第四章: NumPy 基础
正如你可能从第一章中记得的那样,NumPy 是 Python 中科学计算的核心包,提供对基于数组的计算和线性代数的支持。由于 NumPy 是 pandas 的基础,我将在本章介绍其基础知识:解释了 NumPy 数组是什么之后,我们将研究向量化和广播,这两个重要概念允许你编写简洁的数学代码,并在 pandas 中再次遇到。之后,我们将看到为什么 NumPy 提供了称为通用函数的特殊函数,然后通过学习如何获取和设置数组的值以及解释 NumPy 数组的视图和副本之间的区别来结束本章。即使在本书中我们几乎不会直接使用 NumPy,了解其基础知识将使我们更容易学习下一章的 pandas。
使用 NumPy 入门
在这一节中,我们将学习一维和二维 NumPy 数组及背后的技术术语向量化、广播和通用函数。
NumPy 数组
要执行基于数组的计算,如上一章节中遇到的嵌套列表,你需要编写某种形式的循环。例如,要将一个数字添加到嵌套列表中的每个元素,可以使用以下嵌套列表推导式:
In``[``1``]:``matrix``=``[[``1``,``2``,``3``],``[``4``,``5``,``6``],``[``7``,``8``,``9``]]
In``[``2``]:``[[``i``+``1``for``i``in``row``]``for``row``in``matrix``]
Out[2]: [[2, 3, 4], [5, 6, 7], [8, 9, 10]]
这种方法不太易读,更重要的是,如果你使用大数组,逐个元素进行循环会变得非常缓慢。根据你的用例和数组的大小,使用 NumPy 数组而不是 Python 列表可以使计算速度提高几倍甚至提高到一百倍。NumPy 通过利用用 C 或 Fortran 编写的代码来实现这种性能提升——这些是编译性语言,比 Python 快得多。NumPy 数组是一种用于同类型数据的 N 维数组。同类型意味着数组中的所有元素都需要是相同的数据类型。最常见的情况是处理浮点数的一维和二维数组,如图 4-1 所示。
图 4-1 一维和二维 NumPy 数组
让我们创建一个一维和二维数组,在本章节中使用:
In``[``3``]:``# 首先,让我们导入 NumPy``import``numpy``as``np
In``[``4``]:``# 使用一个简单列表构建一个一维数组``array1``=``np``.``array``([``10``,``100``,``1000.``])
In``[``5``]:``# 使用嵌套列表构建一个二维数组``array2``=``np``.``array``([[``1.``,``2.``,``3.``],``[``4.``,``5.``,``6.``]])
数组维度
重要的是要注意一维和二维数组之间的区别:一维数组只有一个轴,因此没有明确的列或行方向。虽然这类似于 VBA 中的数组,但如果你来自像 MATLAB 这样的语言,你可能需要习惯它,因为一维数组在那里始终具有列或行方向。
即使array1
由整数组成,除了最后一个元素(它是一个浮点数),NumPy 数组的同质性强制数组的数据类型为float64
,它能够容纳所有元素。要了解数组的数据类型,请访问其dtype
属性:
In``[``6``]:``array1``.``dtype
Out[6]: dtype('float64')
由于dtype
返回的是float64
而不是我们在上一章中遇到的float
,你可能已经猜到 NumPy 使用了比 Python 数据类型更精细的自己的数值数据类型。不过,通常这不是问题,因为大多数情况下,Python 和 NumPy 之间的不同数据类型转换是自动的。如果你需要将 NumPy 数据类型显式转换为 Python 基本数据类型,只需使用相应的构造函数(我稍后会详细说明如何从数组中访问元素):
In``[``7``]:``float``(``array1``[``0``])
Out[7]: 10.0
要查看 NumPy 数据类型的完整列表,请参阅NumPy 文档。使用 NumPy 数组,你可以编写简单的代码来执行基于数组的计算,我们接下来会看到。
矢量化和广播
如果你构建一个标量和一个 NumPy 数组的和,NumPy 将执行逐元素操作,这意味着你不必自己循环遍历元素。NumPy 社区将这称为矢量化。它允许你编写简洁的代码,几乎与数学符号一样:
In``[``8``]:``array2``+``1
Out[8]: array([[2., 3., 4.], [5., 6., 7.]])
SCALAR
标量指的是像浮点数或字符串这样的基本 Python 数据类型。这是为了将它们与具有多个元素的数据结构如列表和字典或一维和二维 NumPy 数组区分开来。
当你处理两个数组时,相同的原则也适用:NumPy 逐元素执行操作:
In``[``9``]:``array2``*``array2
Out[9]: array([[ 1., 4., 9.], [16., 25., 36.]])
如果你在算术运算中使用两个形状不同的数组,NumPy 会自动将较小的数组扩展到较大的数组,以使它们的形状兼容。这称为广播:
In``[``10``]:``array2``*``array1
Out[10]: array([[ 10., 200., 3000.], [ 40., 500., 6000.]])
要执行矩阵乘法或点积,请使用@
运算符:1
In``[``11``]:``array2``@``array2``.``T``# array2.T 是 array2.transpose()的快捷方式
Out[11]: array([[14., 32.], [32., 77.]])
不要被本节介绍的术语(如标量、向量化或广播)吓到!如果你曾在 Excel 中使用过数组,这些内容应该会感到非常自然,如图 4-2 所示。该截图来自于 array_calculations.xlsx,在配套库的 xl 目录下可以找到。
图 4-2. Excel 中基于数组的计算
你现在知道数组按元素进行算术运算,但如何在数组的每个元素上应用函数呢?这就是通用函数的用处所在。
通用函数(ufunc)
通用函数(ufunc)作用于 NumPy 数组中的每个元素。例如,若在 NumPy 数组上使用 Python 标准的平方根函数math
模块,则会产生错误:
In``[``12``]:``import``math
In``[``13``]:``math``.``sqrt``(``array2``)``# This will raise en Error
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-13-5c37e8f41094> in <module> ----> 1 math.sqrt(array2) # This will raise en Error TypeError: only size-1 arrays can be converted to Python scalars
当然,你也可以编写嵌套循环来获取每个元素的平方根,然后从结果中再次构建一个 NumPy 数组:
In``[``14``]:``np``.``array``([[``math``.``sqrt``(``i``)``for``i``in``row``]``for``row``in``array2``])
Out[14]: array([[1. , 1.41421356, 1.73205081], [2. , 2.23606798, 2.44948974]])
在 NumPy 没有提供 ufunc 且数组足够小的情况下,这种方式可以奏效。然而,若 NumPy 有对应的 ufunc,请使用它,因为它在处理大数组时速度更快——而且更易于输入和阅读:
In``[``15``]:``np``.``sqrt``(``array2``)
Out[15]: array([[1. , 1.41421356, 1.73205081], [2. , 2.23606798, 2.44948974]])
NumPy 的一些 ufunc,例如sum
,也可以作为数组方法使用:如果要对每列求和,则执行以下操作:
In``[``16``]:``array2``.``sum``(``axis``=``0``)``# 返回一个 1 维数组
Out[16]: array([5., 7., 9.])
参数axis=0
表示沿着行的轴,而axis=1
表示沿着列的轴,如图 4-1 所示。若省略axis
参数,则对整个数组求和:
`In
[
17]:
array2.
sum()
Out[17]: 21.0
本书后续部分还会介绍更多 NumPy 的 ufunc,因为它们可与 pandas 的 DataFrame 一起使用。
到目前为止,我们一直在处理整个数组。下一节将向你展示如何操作数组的部分,并介绍一些有用的数组构造函数。
创建和操作数组
在介绍一些有用的数组构造函数之前,我将从获取和设置数组元素的角度开始这一节,包括一种用于生成伪随机数的构造函数,您可以将其用于蒙特卡洛模拟。最后,我将解释数组的视图和副本之间的区别。
获取和设置数组元素
在上一章中,我向您展示了如何索引和切片列表以访问特定元素。当您处理像本章第一个示例中的 matrix
这样的嵌套列表时,您可以使用链式索引:matrix[0][0]
将获得第一行的第一个元素。然而,使用 NumPy 数组时,您可以在单个方括号对中为两个维度提供索引和切片参数:
numpy_array``[``row_selection``,``column_selection``]
对于一维数组,这简化为 numpy_array[selection]
。当您选择单个元素时,将返回一个标量;否则,将返回一个一维或二维数组。请记住,切片表示法使用起始索引(包括)和结束索引(不包括),中间使用冒号,如 start:end
。通过省略开始和结束索引,留下一个冒号,因此该冒号表示二维数组中的所有行或所有列。我在 Figure 4-3 中可视化了一些例子,但您可能也想再看看 Figure 4-1,因为那里标记了索引和轴。请记住,通过对二维数组的列或行进行切片,您最终得到的是一个一维数组,而不是一个二维列或行向量!
图 4-3. 选择 NumPy 数组的元素
运行以下代码来尝试 Figure 4-3 中展示的示例:
In``[``18``]:``array1``[``2``]``# 返回一个标量
Out[18]: 1000.0
In``[``19``]:``array2``[``0``,``0``]``# 返回一个标量
Out[19]: 1.0
In``[``20``]:``array2``[:,``1``:]``# 返回一个 2 维数组
Out[20]: array([[2., 3.], [5., 6.]])
In``[``21``]:``array2``[:,``1``]``# 返回一个 1 维数组
Out[21]: array([2., 5.])
In``[``22``]:``array2``[``1``,``:``2``]``# 返回一个 1 维数组
Out[22]: array([4., 5.])
到目前为止,我通过手动构建示例数组,即通过在列表中提供数字来完成。但是 NumPy 还提供了几个有用的函数来构建数组。
有用的数组构造函数
NumPy 提供了几种构建数组的方法,这些方法对于创建 pandas DataFrames 也很有帮助,正如我们将在 第五章 中看到的那样。一个简单创建数组的方式是使用 arange
函数。这代表数组范围,类似于我们在前一章中遇到的内置 range
函数,不同之处在于 arange
返回一个 NumPy 数组。结合 reshape
可以快速生成具有所需维度的数组:
In``[``23``]:``np``.``arange``(``2``*``5``)``.``reshape``(``2``,``5``)``# 2 行,5 列
Out[23]: array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
另一个常见的需求,例如蒙特卡洛模拟,是生成正态分布的伪随机数数组。NumPy 可以轻松实现这一点:
In``[``24``]:``np``.``random``.``randn``(``2``,``3``)``# 2 行,3 列
Out[24]: array([[-0.30047275, -1.19614685, -0.13652283], [ 1.05769357, 0.03347978, -1.2153504 ]])
其他有用的构造函数包括 np.ones
和 np.zeros
,分别用于创建全为 1 和全为 0 的数组,以及 np.eye
用于创建单位矩阵。我们将在下一章再次遇到其中一些构造函数,但现在让我们学习一下 NumPy 数组视图和副本之间的区别。
视图 vs. 复制
当你切片 NumPy 数组时,它们返回视图。这意味着你在处理原始数组的子集而不复制数据。在视图上设置值也将更改原始数组:
In``[``25``]:``array2
Out[25]: array([[1., 2., 3.], [4., 5., 6.]])
In``[``26``]:``subset``=``array2``[:,``:``2``]``subset
Out[26]: array([[1., 2.], [4., 5.]])
In``[``27``]:``subset``[``0``,``0``]``=``1000
In``[``28``]:``subset
Out[28]: array([[1000., 2.], [ 4., 5.]])
In``[``29``]:``array2
Out[29]: array([[1000., 2., 3.], [ 4., 5., 6.]])
如果这不是你想要的,你需要按照以下方式更改 In [26]
:
subset``=``array2``[:,``:``2``]``.``copy``()
在副本上操作将不会改变原始数组。
结论
在本章中,我向你展示了如何使用 NumPy 数组以及背后的向量化和广播表达式。抛开这些技术术语,使用数组应该感觉非常直观,因为它们紧密遵循数学符号。虽然 NumPy 是一个非常强大的库,但在用于数据分析时有两个主要问题:
-
整个 NumPy 数组都需要是相同的数据类型。例如,这意味着当数组包含文本和数字混合时,你不能执行本章中的任何算术运算。一旦涉及文本,数组将具有数据类型
object
,这将不允许数学运算。 -
-
使用 NumPy 数组进行数据分析使得难以知道每列或每行指的是什么,因为通常通过它们的位置来选择列,比如在
array2[:, 1]
中。
pandas 通过在 NumPy 数组之上提供更智能的数据结构来解决了这些问题。它们是什么以及如何工作将是下一章的主题。
1 如果你已经很久没有上线性代数课了,你可以跳过这个例子——矩阵乘法不是本书的基础。
第五章:使用 pandas 进行数据分析
本章将为您介绍 pandas,即 Python 数据分析库,或者——我喜欢这样说——具有超能力的基于 Python 的电子表格。它非常强大,以至于我曾与一些公司合作时,他们完全放弃了 Excel,而是用 Jupyter 笔记本和 pandas 的组合来替代它。然而,作为本书的读者,我假设您会继续使用 Excel,在这种情况下,pandas 将作为在电子表格中获取数据的接口。pandas 使在 Excel 中特别痛苦的任务变得更加简单、快速和少出错。其中一些任务包括从外部源获取大型数据集以及处理统计数据、时间序列和交互式图表。pandas 最重要的超能力是向量化和数据对齐。正如我们在上一章中看到的使用 NumPy 数组一样,向量化使您能够编写简洁的基于数组的代码,而数据对齐则确保在处理多个数据集时不会出现数据不匹配的情况。
这一章涵盖了整个数据分析过程:从清洗和准备数据开始,然后通过聚合、描述统计和可视化来理解更大的数据集。在本章末尾,我们将看到如何使用 pandas 导入和导出数据。但首先,让我们从介绍 pandas 的主要数据结构开始:DataFrame 和 Series!
DataFrame 和 Series
DataFrame 和 Series 是 pandas 中的核心数据结构。在本节中,我将介绍它们,并重点介绍 DataFrame 的主要组成部分:索引、列和数据。DataFrame 类似于二维 NumPy 数组,但它带有列和行标签,每列可以容纳不同的数据类型。通过从 DataFrame 中提取单列或单行,您会得到一个一维 Series。同样,Series 类似于带有标签的一维 NumPy 数组。当您查看 图 5-1 中 DataFrame 的结构时,您会很容易想象到,DataFrame 就是您基于 Python 的电子表格。
图 5-1. 一个 pandas Series 和 DataFrame
要展示从电子表格转换到 DataFrame 有多容易,请考虑下面的 Excel 表格 图 5-2,它显示了在线课程的参与者及其分数。您可以在伴随仓库的 xl 文件夹中找到相应的文件 course_participants.xlsx。
图 5-2. course_participants.xlsx
要在 Python 中使用这个 Excel 表格,首先导入 pandas,然后使用它的read_excel
函数,该函数返回一个 DataFrame:
In``[``1``]:``import``pandas``as``pd
In``[``2``]:``pd``.``read_excel``(``"xl/course_participants.xlsx"``)
Out[2]: user_id name age country score continent 0 1001 Mark 55 Italy 4.5 Europe 1 1000 John 33 USA 6.7 America 2 1002 Tim 41 USA 3.9 America 3 1003 Jenny 12 Germany 9.0 Europe
PYTHON 3.9 下的 READ_EXCEL 函数
如果你在使用 Python 3.9 或更高版本运行
pd.read_excel
,请确保至少使用 pandas 1.2,否则在读取 xlsx 文件时会出错。
如果你在 Jupyter 笔记本中运行这段代码,DataFrame 将以 HTML 表格的形式进行漂亮的格式化,这使得它与 Excel 中的表格更加接近。我将在第七章中详细介绍使用 pandas 读写 Excel 文件,因此这只是一个介绍性的示例,展示电子表格和 DataFrame 确实非常相似。现在让我们从头开始重新创建这个 DataFrame,而不是从 Excel 文件中读取它:创建 DataFrame 的一种方法是提供数据作为嵌套列表,并为columns
和index
提供值:
In``[``3``]:``data``=``[[``"Mark"``,``55``,``"Italy"``,``4.5``,``"Europe"``],``[``"John"``,``33``,``"USA"``,``6.7``,``"America"``],``[``"Tim"``,``41``,``"USA"``,``3.9``,``"America"``],``[``"Jenny"``,``12``,``"Germany"``,``9.0``,``"Europe"``]]``df``=``pd``.``DataFrame``(``data``=``data``,``columns``=``[``"name"``,``"age"``,``"country"``,``"score"``,``"continent"``],``index``=``[``1001``,``1000``,``1002``,``1003``])``df
Out[3]: name age country score continent 1001 Mark 55 Italy 4.5 Europe 1000 John 33 USA 6.7 America 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe
通过调用info
方法,您将获得一些基本信息,最重要的是数据点的数量和每列的数据类型:
In``[``4``]:``df``.``info``()
<class 'pandas.core.frame.DataFrame'> Int64Index: 4 entries, 1001 to 1003 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 4 non-null object 1 age 4 non-null int64 2 country 4 non-null object 3 score 4 non-null float64 4 continent 4 non-null object dtypes: float64(1), int64(1), object(3) memory usage: 192.0+ bytes
如果你只对列的数据类型感兴趣,请运行df.dtypes
。字符串或混合数据类型的列将具有数据类型object
。1 现在让我们更详细地看一下 DataFrame 的索引和列。
索引
DataFrame 的行标签称为索引。如果没有有意义的索引,请在构造 DataFrame 时将其省略。pandas 将自动创建从零开始的整数索引。我们在从 Excel 文件读取 DataFrame 的第一个示例中看到了这一点。索引将允许 pandas 更快地查找数据,并对许多常见操作至关重要,例如合并两个 DataFrame。您可以像以下方式访问索引对象:
In``[``5``]:``df``.``index
Out[5]: Int64Index([1001, 1000, 1002, 1003], dtype='int64')
如果有意义,给索引起个名字。让我们按照 Excel 表格的方式,并给它命名为user_id
:
In``[``6``]:``df``.``index``.``name``=``"user_id"``df
Out[6]: name age country score continent user_id 1001 Mark 55 Italy 4.5 Europe 1000 John 33 USA 6.7 America 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe
与数据库的主键不同,DataFrame 索引可以具有重复项,但在这种情况下查找值可能较慢。要将索引转换为常规列,请使用reset_index
,要设置新索引,请使用set_index
。如果您不想在设置新索引时丢失现有索引,请确保首先重置它:
In``[``7``]:``# "reset_index"将索引转换为列,用默认索引替换``# 从最开始加载的 DataFrame 对应的数据框``df``.``reset_index``()
Out[7]: user_id name age country score continent 0 1001 Mark 55 Italy 4.5 Europe 1 1000 John 33 USA 6.7 America 2 1002 Tim 41 USA 3.9 America 3 1003 Jenny 12 Germany 9.0 Europe
In``[``8``]:``# "reset_index"将"user_id"转换为常规列,而``# "set_index"将列"name"转换为索引``df``.``reset_index``()``.``set_index``(``"name"``)
Out[8]: user_id age country score continent name Mark 1001 55 Italy 4.5 Europe John 1000 33 USA 6.7 America Tim 1002 41 USA 3.9 America Jenny 1003 12 Germany 9.0 Europe
使用df.reset_index().set_index("name")
时,您正在使用方法链接:因为reset_index()
返回一个 DataFrame,所以您可以直接调用另一个 DataFrame 方法,而不必先编写中间结果。
DATAFRAME METHODS RETURN COPIES
每当您在 DataFrame 上调用形式为
df.method_name()
的方法时,您将获得一个应用了该方法的 DataFrame 副本,保留原始 DataFrame 不变。我们刚刚通过调用df.reset_index()
做到了这一点。如果您想要更改原始 DataFrame,您需要将返回值分配回原始变量,如下所示:
df = df.reset_index()
由于我们没有这样做,这意味着我们的变量
df
仍然保留其原始数据。接下来的示例也调用了 DataFrame 方法,即不更改原始 DataFrame。
要更改索引,请使用 reindex
方法:
In``[``9``]:``df``.``reindex``([``999``,``1000``,``1001``,``1004``])
Out[9]: name age country score continent user_id 999 NaN NaN NaN NaN NaN 1000 John 33.0 USA 6.7 America 1001 Mark 55.0 Italy 4.5 Europe 1004 NaN NaN NaN NaN NaN
这是数据对齐的第一个示例:reindex
将接管所有匹配新索引的行,并将在不存在信息的地方引入带有缺失值(NaN
)的行。你留下的索引元素将被删除。稍后在本章中,我将适当地介绍 NaN
。最后,要对索引进行排序,请使用 sort_index
方法:
In``[``10``]:``df``.``sort_index``()
Out[10]: name age country score continent user_id 1000 John 33 USA 6.7 America 1001 Mark 55 Italy 4.5 Europe 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe
如果你想按一列或多列对行进行排序,请使用 sort_values
:
In``[``11``]:``df``.``sort_values``([``"continent"``,``"age"``])
Out[11]: name age country score continent user_id 1000 John 33 USA 6.7 America 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe 1001 Mark 55 Italy 4.5 Europe
该示例展示了如何首先按 continent
,然后按 age
进行排序。如果只想按一列排序,也可以将列名作为字符串提供:
df.sort_values("continent")
这涵盖了索引如何工作的基础知识。现在让我们将注意力转向其水平对应物,即 DataFrame 的列!
列
要获取 DataFrame 的列信息,请运行以下代码:
`In
[
12]:
df.
columns``
Out[12]: Index(['name', 'age', 'country', 'score', 'continent'], dtype='object')
如果在构建 DataFrame 时没有提供任何列名,pandas 将使用从零开始的整数为列编号。然而,对于列来说,这几乎从来不是一个好主意,因为列表示变量,因此易于命名。你可以像设置索引一样给列头分配一个名称:
In``[``13``]:``df``.``columns``.``name``=``"properties"``df
Out[13]: properties name age country score continent user_id 1001 Mark 55 Italy 4.5 Europe 1000 John 33 USA 6.7 America 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe
如果你不喜欢列名,可以重命名它们:
In``[``14``]:``df``.``rename``(``columns``=``{``"name"``:``"First Name"``,``"age"``:``"Age"``})
Out[14]: properties First Name Age country score continent user_id 1001 Mark 55 Italy 4.5 Europe 1000 John 33 USA 6.7 America 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe
如果要删除列,请使用以下语法(示例显示如何同时删除列和索引):
In``[``15``]:``df``.``drop``(``columns``=``[``"name"``,``"country"``],``index``=``[``1000``,``1003``])
Out[15]: properties age score continent user_id 1001 55 4.5 Europe 1002 41 3.9 America
DataFrame 的列和索引都由一个Index
对象表示,因此可以通过转置 DataFrame 来将列变为行,反之亦然:
In``[``16``]:``df``.``T``# df.transpose()的快捷方式
Out[16]: user_id 1001 1000 1002 1003 properties name Mark John Tim Jenny age 55 33 41 12 country Italy USA USA Germany score 4.5 6.7 3.9 9 continent Europe America America Europe
值得在这里记住的是,我们的 DataFrame df
仍然没有改变,因为我们从未将方法调用返回的 DataFrame 重新分配给原始的 df
变量。如果想要重新排列 DataFrame 的列,可以使用我们与索引一起使用的reindex
方法,但通常更直观的是按所需顺序选择列:
In``[``17``]:``df``.``loc``[:,``[``"continent"``,``"country"``,``"name"``,``"age"``,``"score"``]]
Out[17]: properties continent country name age score user_id 1001 Europe Italy Mark 55 4.5 1000 America USA John 33 6.7 1002 America USA Tim 41 3.9 1003 Europe Germany Jenny 12 9.0
最后这个例子需要解释的地方很多:关于loc
以及数据选择工作方式的所有内容都是下一节的主题。
数据操作
现实世界中的数据很少是一成不变的,因此在处理数据之前,您需要清理数据并将其转换为可消化的形式。我们将从查找如何从 DataFrame 中选择数据开始,如何更改数据,以及如何处理缺失和重复数据。然后,我们将对 DataFrame 执行几个计算,并查看如何处理文本数据。最后,我们将了解 pandas 在返回数据视图与副本时的情况。本节中有许多概念与我们在上一章中使用 NumPy 数组时所见的概念相关。
数据选择
让我们首先在查看其他方法之前,通过标签和位置访问数据,包括布尔索引和使用 MultiIndex 选择数据。
按标签选择
访问 DataFrame 数据的最常见方法是引用其标签。使用属性 loc
,代表位置,指定要检索的行和列:
df``.``loc``[``row_selection``,``column_selection``]
loc
支持切片表示法,因此可以接受冒号来分别选择所有行或列。另外,您还可以提供标签列表以及单个列或行名称。请查看表格 5-1 以查看从我们的样本 DataFrame df
中选择不同部分的几个示例。
表 5-1. 按标签选择数据
选择 | 返回数据类型 | 示例 |
---|---|---|
单个值 | 标量 | df.loc[1000, "country"] |
单列(1d) | Series | df.loc[:, "country"] |
单列(2d) | DataFrame | df.loc[:, ["country"]] |
多列 | DataFrame | df.loc[:, ["country", "age"]] |
列范围 | DataFrame | df.loc[:, "name":"country"] |
单行(1d) | Series | df.loc[1000, :] |
单行(2d) | DataFrame | df.loc[[1000], :] |
多行 | DataFrame | df.loc[[1003, 1000], :] |
行范围 | DataFrame | df.loc[1000:1002, :] |
标签切片具有闭合间隔
使用标签的切片表示法与 Python 和 pandas 中其他一切的工作方式不一致:它们包括上限端点。
应用我们从表格 5-1 中获得的知识,让我们使用 loc
来选择标量、Series 和 DataFrames:
In``[``18``]:``# 对行和列选择使用标量返回标量``df``.``loc``[``1001``,``"name"``]
Out[18]: 'Mark'
In``[``19``]:``# 在行或列选择上使用标量返回 Series``df``.``loc``[[``1001``,``1002``],``"age"``]
Out[19]: user_id 1001 55 1002 41 Name: age, dtype: int64
In``[``20``]:``# 选择多行和多列返回 DataFrame``df``.``loc``[:``1002``,``[``"name"``,``"country"``]]
Out[20]: properties name country user_id 1001 Mark Italy 1000 John USA 1002 Tim USA
重要的是,您要理解 DataFrame 与 Series 之间的区别:即使有单个列,DataFrame 也是二维的,而 Series 是一维的。DataFrame 和 Series 都有索引,但只有 DataFrame 有列标题。当您将列选择为 Series 时,列标题将成为 Series 的名称。许多函数或方法将同时适用于 Series 和 DataFrame,但在执行算术计算时,行为会有所不同:对于 DataFrame,pandas 根据列标题对齐数据—稍后在本章中会详细介绍。
列选择的快捷方式
由于选择列是一个如此常见的操作,pandas 提供了一个快捷方式。而不是:
df``.``loc``[:,``column_selection``]
你可以这样写:
df``[``column_selection``]
例如,
df["country"]
从我们的示例 DataFrame 返回一个 Series,而df[["name", "country"]]
返回一个包含两列的 DataFrame。
按位置选择
通过位置选择 DataFrame 的子集对应于我们在本章开始时使用 NumPy 数组所做的事情。但是,对于 DataFrame,你必须使用 iloc
属性,它代表整数位置:
df``.``iloc``[``row_selection``,``column_selection``]
在使用切片时,你要处理标准的半开区间。表 5-2 给出了与我们之前在 表 5-1 中查看的相同案例。
表 5-2. 按位置选择数据
选择 | 返回数据类型 | 示例 |
---|---|---|
单个值 | Scalar | df.iloc[1, 2] |
一列(1d) | Series | df.iloc[:, 2] |
一列(2d) | DataFrame | df.iloc[:, [2]] |
多列 | DataFrame | df.iloc[:, [2, 1]] |
列范围 | DataFrame | df.iloc[:, :3] |
一行(1d) | Series | df.iloc[1, :] |
一行(2d) | DataFrame | df.iloc[[1], :] |
多行 | DataFrame | df.iloc[[3, 1], :] |
行范围 | DataFrame | df.iloc[1:3, :] |
这是如何使用 iloc
的方法——与之前使用 loc
的样本相同:
In``[``21``]:``df``.``iloc``[``0``,``0``]``# 返回一个 Scalar
Out[21]: 'Mark'
In``[``22``]:``df``.``iloc``[[``0``,``2``],``1``]``# 返回一个 Series
Out[22]: user_id 1001 55 1002 41 Name: age, dtype: int64
In``[``23``]:``df``.``iloc``[:``3``,``[``0``,``2``]]``# 返回一个 DataFrame
Out[23]: properties name country user_id 1001 Mark Italy 1000 John USA 1002 Tim USA
按标签或位置选择数据并不是访问 DataFrame 子集的唯一方式。另一种重要的方式是使用布尔索引;让我们看看它是如何工作的!
按布尔索引选择
布尔索引是指使用仅包含 True
或 False
的 Series 或 DataFrame 来选择 DataFrame 的子集。布尔 Series 用于选择 DataFrame 的特定列和行,而布尔 DataFrame 用于选择整个 DataFrame 中的特定值。最常见的用法是用布尔索引来过滤 DataFrame 的行。可以把它看作是 Excel 中的自动筛选功能。例如,这是如何筛选只显示住在美国且年龄超过 40 岁的人的 DataFrame 的方法:
In``[``24``]:``tf``=``(``df``[``"age"``]``>``40``)``&``(``df``[``"country"``]``==``"USA"``)``tf``# 这是一个仅包含 True/False 的 Series
Out[24]: user_id 1001 False 1000 False 1002 True 1003 False dtype: bool
In``[``25``]:``df``.``loc``[``tf``,``:]
Out[25]: properties name age country score continent user_id 1002 Tim 41 USA 3.9 America
这里有两件事需要解释。首先,由于技术限制,你不能在数据框(DataFrames)中像第三章中那样使用 Python 的布尔运算符。相反,你需要使用如表 5-3 所示的符号。
表 5-3. 布尔运算符
基本 Python 数据类型 | 数据框和 Series |
---|---|
and |
& |
or |
| |
not |
~ |
其次,如果你有多个条件,请确保将每个布尔表达式放在括号中,以避免运算符优先级成为问题:例如,&
的运算符优先级高于==
。因此,如果没有括号,示例中的表达式将被解释为:
df``[``"age"``]``>``(``40``&``df``[``"country"``])``==``"USA"
如果你想要过滤索引,你可以引用它作为df.index
:
In``[``26``]:``df``.``loc``[``df``.``index``>``1001``,``:]
Out[26]: properties name age country score continent user_id 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe
如果你想在基本的 Python 数据结构(如列表)中使用in
操作符,那么在 Series 中使用isin
来过滤你的数据框(DataFrame)以选择来自意大利和德国的参与者:
In``[``27``]:``df``.``loc``[``df``[``"country"``]``.``isin``([``"Italy"``,``"Germany"``]),``:]
Out[27]: properties name age country score continent user_id 1001 Mark 55 Italy 4.5 Europe 1003 Jenny 12 Germany 9.0 Europe
当你使用loc
来提供一个布尔 Series 时,数据框提供了一个特殊的语法,无需loc
即可选择给定完整布尔 DataFrame 的值:
df``[``boolean_df``]
如果你的数据框(DataFrame)仅包含数字,这将特别有帮助。提供一个布尔 DataFrame 将在布尔 DataFrame 为False
时在数据框中返回NaN
。稍后将更详细地讨论NaN
。让我们从创建一个名为rainfall
的新样本数据框开始,其中只包含数字:
In``[``28``]:``# 这可能是以毫米为单位的年降雨量``rainfall``=``pd``.``DataFrame``(``data``=``{``"City 1"``:``[``300.1``,``100.2``],``"City 2"``:``[``400.3``,``300.4``],``"City 3"``:``[``1000.5``,``1100.6``]})``rainfall
Out[28]: City 1 City 2 City 3 0 300.1 400.3 1000.5 1 100.2 300.4 1100.6
In``[``29``]:``rainfall``<``400
Out[29]: City 1 City 2 City 3 0 True False False 1 True True False
In``[``30``]:``rainfall``[``rainfall``<``400``]
Out[30]: City 1 City 2 City 3 0 300.1 NaN NaN 1 100.2 300.4 NaN
注意,在这个例子中,我使用了字典来构建一个新的 DataFrame——如果数据已经以这种形式存在,这通常很方便。以这种方式使用布尔值通常用于过滤特定值,如异常值。
结束数据选择部分之前,我将介绍一种特殊类型的索引称为 MultiIndex。
使用 MultiIndex 进行选择
一个MultiIndex是一个具有多个层级的索引。它允许你按层次分组数据,并轻松访问子集。例如,如果将我们示例 DataFrame df
的索引设置为 continent
和 country
的组合,你可以轻松选择特定大陆的所有行:
In[31]:
# MultiIndex 需要排序df_multi
=df
.reset_index
().
set_index([``"continent"``,``"country"``])
df_multi=
df_multi.
sort_index()``df_multi
Out[31]: properties user_id name age score continent country America USA 1000 John 33 6.7 USA 1002 Tim 41 3.9 Europe Germany 1003 Jenny 12 9.0 Italy 1001 Mark 55 4.5
In[32]:
df_multi``.``loc``[``"Europe"``,``:]
Out[32]: properties user_id name age score country Germany 1003 Jenny 12 9.0 Italy 1001 Mark 55 4.5
请注意,pandas 通过不重复左侧索引级别(大陆)来美化 MultiIndex 的输出。而是在每行更改时仅打印大陆。通过提供元组来选择多个索引级别:
In[33]:
df_multi
.loc``[(``"Europe"``,``"Italy"``),``:]
Out[33]: properties user_id name age score continent country Europe Italy 1001 Mark 55 4.5
如果要选择性地重置 MultiIndex 的部分,请提供级别作为参数。从左侧开始,零是第一列:
In[34]:
df_multi
.reset_index
(level
=0
)`
Out[34]: properties continent user_id name age score country USA America 1000 John 33 6.7 USA America 1002 Tim 41 3.9 Germany Europe 1003 Jenny 12 9.0 Italy Europe 1001 Mark 55 4.5
虽然我们在本书中不会手动创建 MultiIndex,但像groupby
这样的某些操作将导致 pandas 返回带有 MultiIndex 的 DataFrame,因此了解它是很好的。我们将在本章后面介绍groupby
。
现在你知道了各种选择数据的方法,现在是时候学习如何更改数据了。
数据设置
更改 DataFrame 数据的最简单方法是使用loc
或iloc
属性为特定元素分配值。这是本节的起点,在转向操作现有 DataFrame 的其他方法之前:替换值和添加新列。
通过标签或位置设置数据
正如本章前面所指出的,当你调用df.reset_index()
等数据框方法时,该方法总是应用于一个副本,保持原始数据框不变。然而,通过loc
和iloc
属性赋值会改变原始数据框。由于我想保持我们的数据框df
不变,因此在这里我使用了一个称为df2
的副本。如果你想改变单个值,请按照以下步骤操作:
In``[``35``]:``# 先复制数据框以保留原始数据不变``df2``=``df``.``copy``()
In``[``36``]:``df2``.``loc``[``1000``,``"name"``]``=``"JOHN"``df2
Out[36]: properties name age country score continent user_id 1001 Mark 55 Italy 4.5 Europe 1000 JOHN 33 USA 6.7 America 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe
你也可以同时更改多个值。改变 ID 为 1000 和 1001 的用户的分数的一种方法是使用列表:
In``[``37``]:``df2``.``loc``[[``1000``,``1001``],``"score"``]``=``[``3``,``4``]``df2
Out[37]: properties name age country score continent user_id 1001 Mark 55 Italy 4.0 Europe 1000 JOHN 33 USA 3.0 America 1002 Tim 41 USA 3.9 America 1003 Jenny 12 Germany 9.0 Europe
通过位置使用iloc
来改变数据的方式与此相同。现在我们继续看看如何通过布尔索引来改变数据。
通过布尔索引设置数据
布尔索引,我们用来过滤行的方式,也可以用来在数据框中赋值。想象一下,你需要匿名化所有年龄低于 20 岁或来自美国的人的姓名:
In``[``38``]:``tf``=``(``df2``[``"age"``]``<``20``)``|``(``df2``[``"country"``]``==``"USA"``)``df2``.``loc``[``tf``,``"name"``]``=``"xxx"``df2
Out[38]: properties name age country score continent user_id 1001 Mark 55 Italy 4.0 Europe 1000 xxx 33 USA 3.0 America 1002 xxx 41 USA 3.9 America 1003 xxx 12 Germany 9.0 Europe
有时,你有一个数据集,需要跨整个数据框替换某些值,即不特定于某些列。在这种情况下,再次使用特殊语法,并像这样提供整个数据框与布尔值(此示例再次使用rainfall
数据框)。
In``[``39``]:``# 先复制数据框以保留原始数据不变``rainfall2``=``rainfall``.``copy``()``rainfall2
Out[39]: 城市 1 城市 2 城市 3 0 300.1 400.3 1000.5 1 100.2 300.4 1100.6
In``[``40``]:``# 将低于 400 的值设为 0``rainfall2``[``rainfall2``<``400``]``=``0``rainfall2
Out[40]: City 1 City 2 City 3 0 0.0 400.3 1000.5 1 0.0 0.0 1100.6
如果只想用另一个值替换一个值,有一种更简单的方法,我将在下面展示给你。
通过替换值设置数据
如果要在整个 DataFrame 或选定列中替换某个值,请使用replace
方法:
In``[``41``]:``df2``.``replace``(``"USA"``,``"U.S."``)
Out[41]: properties name age country score continent user_id 1001 Mark 55 Italy 4.0 Europe 1000 xxx 33 U.S. 3.0 America 1002 xxx 41 U.S. 3.9 America 1003 xxx 12 Germany 9.0 Europe
如果您只想在country
列上执行操作,您可以改用以下语法:
df2``.``replace``({``"country"``:``{``"USA"``:``"U.S."``}})
在这种情况下,由于USA
只出现在country
列中,它产生了与前一个示例相同的结果。让我们看看如何向 DataFrame 添加额外列,以结束这一节。
通过添加新列设置数据
要向 DataFrame 添加新列,请为新列名称分配值。例如,您可以使用标量或列表向 DataFrame 添加新列:
In``[``42``]:``df2``.``loc``[:,``"discount"``]``=``0``df2``.``loc``[:,``"price"``]``=``[``49.9``,``49.9``,``99.9``,``99.9``]``df2
Out[42]: properties name age country score continent discount price user_id 1001 Mark 55 Italy 4.0 Europe 0 49.9 1000 xxx 33 USA 3.0 America 0 49.9 1002 xxx 41 USA 3.9 America 0 99.9 1003 xxx 12 Germany 9.0 Europe 0 99.9
添加新列通常涉及矢量化计算:
In``[``43``]:``df2``=``df``.``copy``()``# 让我们从头开始复制``df2``。``df2``.``loc``[:,``"birth year"``]``=``2021``-``df2``[``"age"``]``df2
Out[43]: properties name age country score continent birth year user_id 1001 Mark 55 Italy 4.5 Europe 1966 1000 John 33 USA 6.7 America 1988 1002 Tim 41 USA 3.9 America 1980 1003 Jenny 12 Germany 9.0 Europe 2009
我稍后会向你展示更多关于 DataFrame 计算的内容,但在我们到达那之前,请记住我已经多次使用了NaN
吗?下一节将为您提供有关缺失数据主题的更多背景。
缺失数据
缺失数据可能会影响数据分析结果的偏差,从而使你的结论不够健壮。然而,在数据集中有空白是非常常见的,你需要处理它们。在 Excel 中,通常需要处理空单元格或 #N/A
错误,但是 pandas 使用 NumPy 的 np.nan
表示缺失数据,显示为 NaN
。NaN
是浮点数的标准表示为“非数字”。对于时间戳,使用 pd.NaT
,对于文本,pandas 使用 None
。使用 None
或 np.nan
,你可以引入缺失值:
In``[``44``]:``df2``=``df``.``copy``()``# 让我们从一个新的副本开始``df2``.``loc``[``1000``,``"score"``]``=``None``df2``.``loc``[``1003``,``:]``=``None``df2
Out[44]: properties name age country score continent user_id 1001 Mark 55.0 Italy 4.5 Europe 1000 John 33.0 USA NaN America 1002 Tim 41.0 USA 3.9 America 1003 None NaN None NaN None
清理 DataFrame,通常需要删除具有缺失数据的行。这很简单:
In``[``45``]:``df2``.``dropna``()
Out[45]: properties name age country score continent user_id 1001 Mark 55.0 Italy 4.5 Europe 1002 Tim 41.0 USA 3.9 America
然而,如果你只想删除所有值都缺失的行,请使用 how
参数:
In``[``46``]:``df2``.``dropna``(``how``=``"all"``)
Out[46]: properties name age country score continent user_id 1001 Mark 55.0 Italy 4.5 Europe 1000 John 33.0 USA NaN America 1002 Tim 41.0 USA 3.9 America
要获得一个布尔 DataFrame 或 Series,根据是否存在 NaN
,使用 isna
:
In``[``47``]:``df2``.``isna``()
Out[47]: properties name age country score continent user_id 1001 False False False False False 1000 False False False True False 1002 False False False False False 1003 True True True True True
要填充缺失值,使用 fillna
。例如,将分数列中的 NaN
替换为其平均值(稍后我将介绍描述统计信息如 mean
):
In``[``48``]:``df2``.``fillna``({``"score"``:``df2``[``"score"``]``.``mean``()})
Out[48]: properties name age country score continent user_id 1001 Mark 55.0 Italy 4.5 Europe 1000 John 33.0 USA 4.2 America 1002 Tim 41.0 USA 3.9 America 1003 None NaN None 4.2 None
缺失数据不是唯一需要清理数据集的条件。对于重复数据也是如此,所以让我们看看我们的选择!
重复数据
像缺失数据一样,重复项会对分析的可靠性产生负面影响。要删除重复行,请使用drop_duplicates
方法。您可以选择提供一列子集作为参数:
In``[``49``]:``df``.``drop_duplicates``([``"country"``,``"continent"``])
Out[49]: properties name age country score continent user_id 1001 Mark 55 Italy 4.5 Europe 1000 John 33 USA 6.7 America 1003 Jenny 12 Germany 9.0 Europe
默认情况下,这将保留第一次出现。要查找某列是否包含重复项或获取其唯一值,请使用以下两个命令(如果您希望在索引上运行此操作,请使用df.index
而不是df["country"]
):
In``[``50``]:``df``[``"country"``]``.``is_unique
Out[50]: False
`In
[
51]:
df[
"country"]
.unique
()
Out[51]: array(['Italy', 'USA', 'Germany'], dtype=object)
最后,要了解哪些行是重复的,请使用duplicated
方法,它返回一个布尔值系列:默认情况下,它使用参数keep="first"
,保留第一次出现并仅标记重复为True
。通过设置参数keep=False
,它将对所有行返回True
,包括第一次出现,从而轻松获取包含所有重复行的 DataFrame。在以下示例中,我们查看country
列是否有重复,但实际上,您经常查看索引或整个行。在这种情况下,您必须使用df.index.duplicated()
或df.duplicated()
代替:
In``[``52``]:``# 默认情况下,它仅标记重复项为 True,即没有第一次出现``df``[``"country"``]``.``duplicated``()
Out[52]: user_id 1001 False 1000 False 1002 True 1003 False Name: country, dtype: bool
In``[``53``]:``# 要获取所有重复
"country"的行,请使用``keep=False``df``.``loc``[``df``[``"country"``]``.``duplicated``(``keep``=``False``),``:]
Out[53]: properties name age country score continent user_id 1000 John 33 USA 6.7 America 1002 Tim 41 USA 3.9 America
一旦您通过删除缺失和重复数据清理了您的 DataFrame,您可能希望执行一些算术运算-下一节将为您介绍如何执行这些操作。
算术运算
像 NumPy 数组一样,DataFrame 和 Series 利用向量化。例如,要将数字添加到rainfall
DataFrame 中的每个值,只需执行以下操作:
In``[``54``]:``rainfall
Out[54]: City 1 City 2 City 3 0 300.1 400.3 1000.5 1 100.2 300.4 1100.6
In``[``55``]:``rainfall``+``100
Out[55]: City 1 City 2 City 3 0 400.1 500.3 1100.5 1 200.2 400.4 1200.6
然而,pandas 的真正力量在于其自动数据对齐机制:当你使用多个 DataFrame 进行算术运算时,pandas 会自动根据它们的列和行索引进行对齐。让我们创建第二个具有一些相同行和列标签的 DataFrame,然后进行求和操作:
In``[``56``]:``more_rainfall``=``pd``.``DataFrame``(``data``=``[[``100``,``200``],``[``300``,``400``]],``index``=``[``1``,``2``],``columns``=``[``"City 1"``,``"City 4"``])``more_rainfall
Out[56]: City 1 City 4 1 100 200 2 300 400
In``[``57``]:``rainfall``+``more_rainfall
Out[57]: City 1 City 2 City 3 City 4 0 NaN NaN NaN NaN 1 200.2 NaN NaN NaN 2 NaN NaN NaN NaN
结果 DataFrame 的索引和列是两个 DataFrame 索引和列的并集:那些两个 DataFrame 中都有值的字段显示其和,而其余的 DataFrame 显示 NaN
。如果你来自 Excel,这可能是你需要适应的地方,因为在 Excel 中,当你在算术运算中使用空单元格时,它们会自动转换为零。要获得与 Excel 中相同的行为,请使用 add
方法并使用 fill_value
替换 NaN
值为零:
In``[``58``]:``rainfall``.``add``(``more_rainfall``,``fill_value``=``0``)
Out[58]: City 1 City 2 City 3 City 4 0 300.1 400.3 1000.5 NaN 1 200.2 300.4 1100.6 200.0 2 300.0 NaN NaN 400.0
这对于其他算术运算符也适用,如表 Table 5-4 所示。
Table 5-4. 算术运算符
Operator | Method |
---|---|
* |
mul |
+ |
add |
- |
sub |
/ |
div |
** |
pow |
当你在计算中有一个 DataFrame 和一个 Series 时,默认情况下 Series 会沿着索引进行广播:
In``[``59``]:``# 从一行中提取的 Series``rainfall``.``loc``[``1``,``:]
Out[59]: City 1 100.2 City 2 300.4 City 3 1100.6 Name: 1, dtype: float64
In``[``60``]:``rainfall``+``rainfall``.``loc``[``1``,``:]
Out[60]: City 1 City 2 City 3 0 400.3 700.7 2101.1 1 200.4 600.8 2201.2
因此,要按列添加一个 Series,你需要使用 add
方法并显式指定 axis
参数:
In``[``61``]:``# 从一列中提取的 Series``rainfall``.``loc``[:,``"City 2"``]
Out[61]: 0 400.3 1 300.4 Name: City 2, dtype: float64
In``[``62``]:``rainfall``.``add``(``rainfall``.``loc``[:,``"City 2"``],``axis``=``0``)
Out[62]: City 1 City 2 City 3 0 700.4 800.6 1400.8 1 400.6 600.8 1401.0
虽然本节讨论的是带有数字的 DataFrame 在算术运算中的行为,但下一节将展示你在处理 DataFrame 中文本时的选项。
处理文本列
正如我们在本章开头所见,包含文本或混合数据类型的列具有数据类型object
。要对包含文本字符串的列执行操作,请使用str
属性,该属性使您可以访问 Python 的字符串方法。我们在第三章中已经了解了一些字符串方法,但查看一下Python 文档中提供的方法也无妨。例如,要去除前导和尾随空格,请使用strip
方法;要将所有首字母大写,可以使用capitalize
方法。将这些方法链在一起将清理手动输入数据产生的混乱文本列:
In``[``63``]:``# 让我们创建一个新的 DataFrame``users``=``pd``.``DataFrame``(``data``=``[``" mArk "``,``"JOHN "``,``"Tim"``,``" jenny"``],``columns``=``[``"name"``])``users
Out[63]: name 0 mArk 1 JOHN 2 Tim 3 jenny
In``[``64``]:``users_cleaned``=``users``.``loc``[:,``"name"``]``.``str``.``strip``()``.``str``.``capitalize``()``users_cleaned
Out[64]: 0 Mark 1 John 2 Tim 3 Jenny Name: name, dtype: object
或者,要查找所有以“J”开头的名称:
In``[``65``]:``users_cleaned``.``str``.``startswith``(``"J"``)
Out[65]: 0 False 1 True 2 False 3 True Name: name, dtype: bool
字符串方法很容易使用,但有时您可能需要以不内置的方式操作 DataFrame。在这种情况下,创建自己的函数并将其应用于 DataFrame,如下一节所示。
应用函数
数据框提供了applymap
方法,该方法将应用于每个单独的元素,如果没有 NumPy ufuncs 可用,则非常有用。例如,没有用于字符串格式化的 ufuncs,因此我们可以像这样格式化 DataFrame 的每个元素:
In``[``66``]:``rainfall
Out[66]: City 1 City 2 City 3 0 300.1 400.3 1000.5 1 100.2 300.4 1100.6
In``[``67``]:``def``format_string``(``x``):``return``f``"{x:,.2f}"
In``[``68``]:``# 注意,我们传递函数时不要调用它,即 format_string 而不是 format_string()!``rainfall``.``applymap``(``format_string``)
Out[68]: City 1 City 2 City 3 0 300.10 400.30 1,000.50 1 100.20 300.40 1,100.60
要分解这个过程:下面的 f-string 将x
返回为一个字符串:f"{x}"
。要添加格式化,将冒号附加到变量后面,然后是格式化字符串,.2f
。逗号是千位分隔符,.2f
表示小数点后两位的固定点表示法。要获取有关如何格式化字符串的更多详细信息,请参阅格式规范迷你语言,它是 Python 文档的一部分。
对于这种用例,lambda 表达式(见侧边栏)被广泛使用,因为它们允许你在一行内写出同样的内容,而无需定义单独的函数。利用 lambda 表达式,我们可以将前面的例子重写为以下形式:
In``[``69``]:``降雨量``.``applymap``(``lambda``x``:``f``"{x:,.2f}"``)
Out[69]: 城市 1 城市 2 城市 3 0 300.10 400.30 1,000.50 1 100.20 300.40 1,100.60
LAMBDA 表达式
Python 允许你通过 lambda 表达式在一行内定义函数。Lambda 表达式是匿名函数,这意味着它是一个没有名称的函数。考虑这个函数:
def``函数名``(``参数 1``,``参数 2``,``...``):``return``返回值
这个函数可以重写为如下的 lambda 表达式:
lambda``参数 1``,``参数 2``,``...``:``返回值
本质上,你用 lambda 替换
def
,省略return
关键字和函数名,并把所有内容放在一行上。就像我们在applymap
方法中看到的那样,在这种情况下,这样做非常方便,因为我们不需要为仅被使用一次的事情定义一个函数。
我已经提到了所有重要的数据操作方法,但在我们继续之前,理解 pandas 何时使用数据框的视图和何时使用副本是很重要的。
视图 vs. 副本
你可能还记得上一章节中,切片 NumPy 数组返回一个视图。但是对于数据框而言,情况更加复杂:loc
和 iloc
是否返回视图或副本往往难以预测,这使得它成为比较令人困惑的话题之一。因为改变视图和数据框副本是很大的区别,当 pandas 认为你以不合适的方式设置数据时,它经常会提出如下警告:SettingWithCopyWarning
。为了避免这种颇为神秘的警告,这里有一些建议:
-
在原始数据框上设置值,而不是在从另一个数据框切片得到的数据框上设置值
-
-
如果你想要在切片后得到一个独立的数据框,那么要显式地复制:
-
选择``=``df``.``loc``[:,``[``"国家"``,``"大陆"``]]``.``copy``()
虽然在处理 loc
和 iloc
时情况复杂,但值得记住的是,所有数据框方法如 df.dropna()
或 df.sort_values("列名")
总是返回一个副本。
到目前为止,我们大多数时间都是在处理一个数据框。接下来的章节将展示多种将多个数据框合并为一个的方法,这是 pandas 提供的一个非常常见的强大工具。
合并数据框
在 Excel 中组合不同的数据集可能是一个繁琐的任务,通常涉及大量的VLOOKUP
公式。幸运的是,pandas 的合并 DataFrame 功能是其杀手级功能之一,其数据对齐能力将极大地简化你的生活,从而大大减少引入错误的可能性。合并和连接 DataFrame 可以通过各种方式进行;本节只讨论使用concat
、join
和merge
的最常见情况。虽然它们有重叠之处,但每个函数都使特定任务变得非常简单。我将从concat
函数开始,然后解释使用join
的不同选项,最后介绍最通用的merge
函数。
连接
简单地将多个 DataFrame 粘合在一起,concat
函数是你的好帮手。正如函数名称所示,这个过程有一个技术名字叫做连接。默认情况下,concat
沿着行将 DataFrame 粘合在一起,并自动对齐列。在下面的示例中,我创建了另一个 DataFrame more_users
,并将其附加到我们样本 DataFrame df
的底部:
In``[``70``]:``data``=``[[``15``,``"法国"``,``4.1``,``"贝基"``],``[``44``,``"加拿大"``,``6.1``,``"莉安"``]]``more_users``=``pd``.``DataFrame``(``data``=``data``,``columns``=``[``"年龄"``,``"国家"``,``"得分"``,``"姓名"``],``index``=``[``1000``,``1011``])``more_users
Out[70]: 年龄 国家 得分 姓名 1000 15 法国 4.1 贝基 1011 44 加拿大 6.1 莉安
In``[``71``]:``pd``.``concat``([``df``,``more_users``],``axis``=``0``)
Out[71]: 姓名 年龄 国家 得分 大陆 1001 马克 55 意大利 4.5 欧洲 1000 约翰 33 美国 6.7 美洲 1002 蒂姆 41 美国 3.9 美洲 1003 珍妮 12 德国 9.0 欧洲 1000 贝基 15 法国 4.1 NaN 1011 莉安 44 加拿大 6.1 NaN
现在你注意到,由于concat
在指定轴(行)上将数据粘合在一起,并且仅在另一个轴(列)上对齐数据,所以你现在有重复的索引元素!即使两个 DataFrame 中的列名不同序,它们也会自动匹配列名!如果你想沿着列将两个 DataFrame 粘合在一起,请设置axis=1
:
In``[``72``]:``data``=``[[``3``,``4``],``[``5``,``6``]]``more_categories``=``pd``.``DataFrame``(``data``=``data``,``columns``=``[``"测验"``,``"登录"``],``index``=``[``1000``,``2000``])``more_categories
Out[72]: 测验 登录 1000 3 4 2000 5 6
In``[``73``]:``pd``.``concat``([``df``,``more_categories``],``axis``=``1``)
Out[73]: name age country score continent quizzes logins 1000 John 33.0 USA 6.7 America 3.0 4.0 1001 Mark 55.0 Italy 4.5 Europe NaN NaN 1002 Tim 41.0 USA 3.9 America NaN NaN 1003 Jenny 12.0 Germany 9.0 Europe NaN NaN 2000 NaN NaN NaN NaN NaN 5.0 6.0
concat
的特殊且非常有用的特性是它可以接受超过两个 DataFrame。我们将在下一章节中使用它将多个 CSV 文件合并成一个单独的 DataFrame:
pd``.``concat``([``df1``,``df2``,``df3``,``...``])
另一方面,join
和 merge
仅适用于两个 DataFrame,下面我们将看到。
连接与合并
当你连接两个 DataFrame 时,你将每个 DataFrame 的列合并到一个新的 DataFrame 中,同时根据集合理论决定行的处理方式。如果你之前有过与关系数据库的工作经验,那么这与 SQL 查询中的 JOIN
子句是相同的概念。图 5-3 显示了内连接、左连接、右连接和外连接四种连接类型如何通过使用两个示例 DataFrame df1
和 df2
进行操作。
图 5-3. 连接类型
使用 join
方法,pandas 使用两个 DataFrame 的索引来对齐行。内连接返回仅在索引重叠的行的 DataFrame。左连接获取左侧 DataFrame df1
的所有行,并在右侧 DataFrame df2
上匹配索引。在 df2
中没有匹配行的地方,pandas 将填充 NaN
。左连接对应于 Excel 中的 VLOOKUP
情况。右连接获取右表 df2
的所有行,并将它们与 df1
的行在索引上匹配。最后,全外连接(即完全外连接)获取两个 DataFrame 的索引的并集,并在可能的情况下匹配值。表 5-5 是文本形式中 图 5-3 的等效内容。
表 5-5. 连接类型
类型 | 描述 |
---|---|
inner |
仅包含索引存在于两个 DataFrame 中的行 |
left |
从左 DataFrame 中获取所有行,匹配右 DataFrame 的行 |
right |
从右 DataFrame 中获取所有行,匹配左 DataFrame 的行 |
outer |
从两个 DataFrame 中获取所有行的并集 |
让我们看看实际操作中的情况,将 图 5-3 中的示例活现出来:
In``[``74``]:``df1``=``pd``.``DataFrame``(``data``=``[[``1``,``2``],``[``3``,``4``],``[``5``,``6``]],``columns``=``[``"A"``,``"B"``])``df1
Out[74]: A B 0 1 2 1 3 4 2 5 6
In``[``75``]:``df2``=``pd``.``DataFrame``(``data``=``[[``10``,``20``],``[``30``,``40``]],``columns``=``[``"C"``,``"D"``],``index``=``[``1``,``3``])``df2
Out[75]: C D 1 10 20 3 30 40
In``[``76``]:``df1``.``join``(``df2``,``how``=``"inner"``)
Out[76]: A B C D 1 3 4 10 20
In``[``77``]:``df1``.``join``(``df2``,``how``=``"left"``)
Out[77]: A B C D 0 1 2 NaN NaN 1 3 4 10.0 20.0 2 5 6 NaN NaN
In``[``78``]:``df1``.``join``(``df2``,``how``=``"right"``)
Out[78]: A B C D 1 3.0 4.0 10 20 3 NaN NaN 30 40
In``[``79``]:``df1``.``join``(``df2``,``how``=``"outer"``)
Out[79]: A B C D 0 1.0 2.0 NaN NaN 1 3.0 4.0 10.0 20.0 2 5.0 6.0 NaN NaN 3 NaN NaN 30.0 40.0
如果你想要根据一个或多个 DataFrame 列进行连接而不是依赖索引,使用merge
而不是join
。merge
接受on
参数作为连接条件:这些列必须存在于两个 DataFrame 中,并用于匹配行:
In``[``80``]:``# 给两个 DataFrame 都添加一个名为"category"的列``df1``[``"category"``]``=``[``"a"``,``"b"``,``"c"``]``df2``[``"category"``]``=``[``"c"``,``"b"``]
In``[``81``]:``df1
Out[81]: A B category 0 1 2 a 1 3 4 b 2 5 6 c
In``[``82``]:``df2
Out[82]: C D category 1 10 20 c 3 30 40 b
In``[``83``]:``df1``.``merge``(``df2``,``how``=``"inner"``,``on``=``[``"category"``])
Out[83]: A B category C D 0 3 4 b 30 40 1 5 6 c 10 20
In``[``84``]:``df1``.``merge``(``df2``,``how``=``"left"``,``on``=``[``"category"``])
Out[84]: A B category C D 0 1 2 a NaN NaN 1 3 4 b 30.0 40.0 2 5 6 c 10.0 20.0
由于join
和merge
接受许多可选参数来适应更复杂的场景,我建议你查看官方文档以了解更多信息。
你现在知道如何操作一个或多个 DataFrame,这将引导我们数据分析旅程的下一步:理解数据。
描述性统计和数据聚合
在理解大数据集的一种方法是计算描述统计,如总和或平均值,可以针对整个数据集或有意义的子集。本节首先介绍了在 pandas 中如何进行这种操作,然后介绍了两种数据聚合到子集的方式:groupby
方法和pivot_table
函数。
描述性统计
描述性统计允许你通过使用定量的方法对数据集进行总结。例如,数据点的数量是一个简单的描述性统计量。像均值、中位数或众数这样的平均数也是其他流行的例子。DataFrame 和 Series 允许你通过诸如sum
、mean
和count
之类的方法方便地访问描述性统计信息。在本书中你将会遇到许多这样的方法,并且完整的列表可以通过pandas 文档获取。默认情况下,它们返回沿着axis=0
的 Series,这意味着你得到了列的统计信息:
In``[``85``]:``rainfall
Out[85]: City 1 City 2 City 3 0 300.1 400.3 1000.5 1 100.2 300.4 1100.6
In``[``86``]:``rainfall``.``mean``()
Out[86]: City 1 200.15 City 2 350.35 City 3 1050.55 dtype: float64
如果需要每行的统计信息,请提供axis
参数:
In``[``87``]:``rainfall``.``mean``(``axis``=``1``)
Out[87]: 0 566.966667 1 500.400000 dtype: float64
默认情况下,描述性统计不包括缺失值,这与 Excel 处理空单元格的方式一致,因此在带有空单元格的范围上使用 Excel 的AVERAGE
公式将给出与在具有相同数字和NaN
值而不是空单元格的 Series 上应用mean
方法相同的结果。
有时仅仅获取 DataFrame 所有行的统计数据是不够的,你需要更详细的信息——例如每个类别的平均值。让我们看看如何实现!
分组
使用我们的示例 DataFrame df
,让我们再次找出每个大陆的平均分数!为此,首先按大陆分组行,然后应用mean
方法,该方法将计算每个组的平均值。所有非数字列将自动排除:
In``[``88``]:``df``.``groupby``([``"continent"``])``.``mean``()
Out[88]: properties age score continent America 37.0 5.30 Europe 33.5 6.75
如果包括多于一个列,生成的 DataFrame 将具有层次化索引——我们之前遇到的 MultiIndex:
In``[``89``]:``df``.``groupby``([``"continent"``,``"country"``])``.``mean``()
Out[89]: properties age score continent country America USA 37 5.3 Europe Germany 12 9.0 Italy 55 4.5
你可以使用大多数由 pandas 提供的描述性统计量,如果想使用自己的函数,可以使用agg
方法。例如,这是如何获取每个组最大值与最小值之差的方法:
In``[``90``]:``df``.``groupby``([``"continent"``])``.``agg``(``lambda``x``:``x``.``max``()``-``x``.``min``())
Out[90]: properties age score continent America 8 2.8 Europe 43 4.5
在 Excel 中,获取每个分组的统计数据的一种流行方式是使用数据透视表。它们引入了第二个维度,非常适合从不同角度查看数据。pandas 也有数据透视表功能,我们接下来会看到。
数据透视和数据融合
如果您在 Excel 中使用数据透视表,可以毫不费力地应用 pandas 的pivot_table
函数,因为它的使用方式基本相同。下面的 DataFrame 中的数据组织方式与通常在数据库中存储记录的方式类似;每一行显示了特定水果在特定地区的销售交易:
In``[``91``]:``data``=``[[``"Oranges"``,``"North"``,``12.30``],``[``"Apples"``,``"South"``,``10.55``],``[``"Oranges"``,``"South"``,``22.00``],``[``"Bananas"``,``"South"``,``5.90``],``[``"Bananas"``,``"North"``,``31.30``],``[``"Oranges"``,``"North"``,``13.10``]]``sales``=``pd``.``DataFrame``(``data``=``data``,``columns``=``[``"Fruit"``,``"Region"``,``"Revenue"``])``sales
Out[91]: Fruit Region Revenue 0 Oranges North 12.30 1 Apples South 10.55 2 Oranges South 22.00 3 Bananas South 5.90 4 Bananas North 31.30 5 Oranges North 13.10
要创建数据透视表,将 DataFrame 作为pivot_table
函数的第一个参数提供。index
和columns
定义了 DataFrame 的哪一列将成为数据透视表的行和列标签。values
将根据aggfunc
聚合到生成的 DataFrame 的数据部分中,aggfunc
是一个可以作为字符串或 NumPy ufunc 提供的函数。最后,margins
对应于 Excel 中的Grand Total
,即如果不指定margins
和margins_name
,则不会显示Total
列和行:
In``[``92``]:``pivot``=``pd``.``pivot_table``(``sales``,``index``=``"Fruit"``,``columns``=``"Region"``,``values``=``"Revenue"``,``aggfunc``=``"sum"``,``margins``=``True``,``margins_name``=``"Total"``)``pivot
Out[92]: Region North South Total Fruit Apples NaN 10.55 10.55 Bananas 31.3 5.90 37.20 Oranges 25.4 22.00 47.40 Total 56.7 38.45 95.15
总之,对数据进行透视意味着获取某一列的唯一值(在我们的例子中是Region
),并将其转换为数据透视表的列标题,从而聚合另一列的值。这样可以轻松地查看所关心维度的汇总信息。在我们的数据透视表中,您立即可以看到北部地区没有销售苹果,而南部地区的大部分收入来自橙子。如果您希望反过来,将列标题转换为单列的值,请使用melt
。从这个意义上说,melt
是pivot_table
函数的反义词:
In``[``93``]:``pd``.``melt``(``pivot``.``iloc``[:``-``1``,:``-``1``]``.``reset_index``(),``id_vars``=``"Fruit"``,``value_vars``=``[``"North"``,``"South"``],``value_name``=``"Revenue"``)
Out[93]: Fruit Region Revenue 0 Apples North NaN 1 Bananas North 31.30 2 Oranges North 25.40 3 Apples South 10.55 4 Bananas South 5.90 5 Oranges South 22.00
在这里,我将我们的透视表作为输入提供,但我使用iloc
来去除总行和列。我还重置索引,以便所有信息都作为常规列可用。然后,我提供id_vars
以指示标识符,并提供value_vars
以定义我要“解构”的列。如果您希望准备数据以便将其存储回预期以此格式存储的数据库中,熔断可能会很有用。
使用聚合统计数据有助于理解数据,但没有人喜欢阅读满篇数字。为了使信息易于理解,创建可视化效果最有效,这是我们接下来要讨论的内容。虽然 Excel 使用术语图表,但 pandas 通常称之为图。在本书中,我会交替使用这些术语。
绘图
绘图允许您可视化数据分析的发现,可能是整个过程中最重要的一步。对于绘图,我们将使用两个库:首先是 Matplotlib,pandas 的默认绘图库,然后是 Plotly,这是一个现代绘图库,在 Jupyter 笔记本中提供更交互式的体验。
Matplotlib
Matplotlib 是一个长期存在的绘图包,包含在 Anaconda 发行版中。您可以使用它生成多种格式的图表,包括高质量打印的矢量图形。当您调用 DataFrame 的plot
方法时,pandas 默认会生成一个 Matplotlib 图。
要在 Jupyter 笔记本中使用 Matplotlib,您需要首先运行两个魔术命令中的一个(参见侧边栏“魔术命令”):%matplotlib inline
或%matplotlib notebook
。它们配置笔记本以便在笔记本本身中显示图表。后者命令增加了一些交互性,允许您更改图表的大小或缩放系数。让我们开始,并使用 pandas 和 Matplotlib 创建第一个图表(见图 5-4):
In``[``94``]:``import``numpy``as``np``%``matplotlib``inline``# Or %matplotlib notebook
In``[``95``]:``data``=``pd``.``DataFrame``(``data``=``np``.``random``.``rand``(``4``,``4``)``*``100000``,``index``=``[``"Q1"``,``"Q2"``,``"Q3"``,``"Q4"``],``columns``=``[``"East"``,``"West"``,``"North"``,``"South"``])``data``.``index``.``name``=``"Quarters"``data``.``columns``.``name``=``"Region"``data
Out[95]: Region East West North South Quarters Q1 23254.220271 96398.309860 16845.951895 41671.684909 Q2 87316.022433 45183.397951 15460.819455 50951.465770 Q3 51458.760432 3821.139360 77793.393899 98915.952421 Q4 64933.848496 7600.277035 55001.831706 86248.512650
`In
[
96]:
data.
plot()
# 快捷方式用于 data.plot.line()``
Out[96]: <AxesSubplot:xlabel='Quarters'>
图 5-4. Matplotlib 绘图
请注意,在此示例中,我使用了 NumPy 数组来构建 pandas DataFrame。提供 NumPy 数组允许你利用上一章介绍的 NumPy 构造函数;在这里,我们使用 NumPy 根据伪随机数生成了一个 pandas DataFrame。因此,当你在自己的环境中运行示例时,会得到不同的值。
魔术命令
我们在 Jupyter 笔记本中使用的
%matplotlib inline
命令是一个魔术命令。魔术命令是一组简单的命令,可以让 Jupyter 笔记本单元格以特定方式运行,或者使繁琐的任务变得异常简单,几乎像魔术一样。你可以像写 Python 代码一样在单元格中编写这些命令,但它们以%%
或%
开头。影响整个单元格的命令以%%
开头,而仅影响单行的命令以%
开头。我们将在接下来的章节中看到更多的魔术命令,但如果你想列出当前所有可用的魔术命令,请运行
%lsmagic
,并运行%magic
获取详细说明。
即使使用 %matplotlib notebook
魔术命令,你可能会注意到 Matplotlib 最初设计用于静态绘图,而不是在网页上进行交互体验。这就是为什么接下来我们将使用 Plotly,这是一个专为 Web 设计的绘图库。
Plotly
Plotly 是基于 JavaScript 的库,自版本 4.8.0 起,可以作为 pandas 的绘图后端,具有出色的交互性:你可以轻松缩放,单击图例以选择或取消选择类别,并获取有关悬停数据点的更多信息。Plotly 不包含在 Anaconda 安装中,因此如果尚未安装,请通过运行以下命令安装:
(base)>
conda install plotly
运行此单元格后,整个笔记本的绘图后端将设置为 Plotly,如果重新运行之前的单元格,它也将呈现为 Plotly 图表。对于 Plotly,你只需在能够绘制 5-5 和 5-6 图形之前将其设置为后端:
In``[``97``]:``# 将绘图后端设置为 Plotly``pd``.``options``.``plotting``.``backend``=``"plotly"
`In
[
98]:
data.
plot()
图 5-5. Plotly 折线图
In``[``99``]:``# 显示相同数据的条形图``data``.``plot``.``bar``(``barmode``=``"group"``)
图 5-6. Plotly 条形图
绘图后端的差异
如果您使用 Plotly 作为绘图后端,您需要直接在 Plotly 文档中检查绘图方法的接受参数。例如,您可以查看 Plotly 条形图文档 中的
barmode=group
参数。
pandas 和底层绘图库提供了丰富的图表类型和选项,可以以几乎任何期望的方式格式化图表。也可以将多个图表安排到子图系列中。总览见 表 5-6 显示了可用的绘图类型。
表 5-6. pandas 绘图类型
类型 | 描述 |
---|---|
line |
线性图表,默认运行 df.plot() 时的默认设置 |
bar |
垂直条形图 |
barh |
水平条形图 |
hist |
直方图 |
box |
箱线图 |
kde |
密度图,也可以通过 density 使用 |
area |
面积图 |
scatter |
散点图 |
hexbin |
六角形箱形图 |
pie |
饼图 |
此外,pandas 还提供了一些由多个独立组件组成的高级绘图工具和技术。详情请见 pandas 可视化文档。
其他绘图库
Python 的科学可视化领域非常活跃,除了 Matplotlib 和 Plotly 外,还有许多其他高质量的选择,可能更适合特定用例:
Seaborn
Seaborn 建立在 Matplotlib 之上。它改进了默认样式,并添加了额外的绘图,如热图,通常简化了您的工作:您可以仅使用几行代码创建高级统计图。
Bokeh
Bokeh 类似于 Plotly 在技术和功能上:它基于 JavaScript,因此在 Jupyter 笔记本中也非常适合交互式图表。Bokeh 包含在 Anaconda 中。
Altair
Altair 是一个基于 Vega 项目 的统计可视化库。Altair 也是基于 JavaScript 的,提供一些像缩放这样的交互功能。
HoloViews
HoloViews 是另一个基于 JavaScript 的包,专注于使数据分析和可视化变得简单。只需几行代码,您就可以实现复杂的统计图。
我们将在下一章节创建更多的图表来分析时间序列,但在此之前,让我们通过学习如何使用 pandas 导入和导出数据来结束本章节!
导入和导出数据帧
到目前为止,我们使用嵌套列表、字典或 NumPy 数组从头构建了 DataFrame。了解这些技术很重要,但通常数据已经可用,您只需将其转换为 DataFrame。为此,pandas 提供了各种读取函数。但即使您需要访问 pandas 不提供内置读取器的专有系统,您通常也可以使用 Python 包连接到该系统,一旦获取数据,将其转换为 DataFrame 就很容易。在 Excel 中,数据导入通常是使用 Power Query 处理的工作类型。
分析和更改数据集后,您可能希望将结果推回数据库或将其导出为 CSV 文件或者——考虑到本书的标题——将其呈现在 Excel 工作簿中供您的经理查看。要导出 pandas DataFrame,请使用 DataFrame 提供的导出器方法之一。表格 5-7 显示了最常见的导入和导出方法概述。
表格 5-7. 导入和导出数据框
数据格式/系统 | 导入:pandas(pd)函数 | 导出:DataFrame(df)方法 |
---|---|---|
CSV 文件 | pd.read_csv |
df.to_csv |
JSON | pd.read_json |
df.to_json |
HTML | pd.read_html |
df.to_html |
剪贴板 | pd.read_clipboard |
df.to_clipboard |
Excel 文件 | pd.read_excel |
df.to_excel |
SQL 数据库 | pd.read_sql |
df.to_sql |
我们将在第十一章中遇到 pd.read_sql
和 pd.to_sql
,在那里我们将把它们作为案例研究的一部分使用。并且由于我打算在整个第七章中专门讨论使用 pandas 读取和写入 Excel 文件的主题,因此在本节中我将重点讨论导入和导出 CSV 文件。让我们从导出现有的 DataFrame 开始!
导出 CSV 文件
如果您需要将 DataFrame 传递给可能不使用 Python 或 pandas 的同事,以 CSV 文件的形式传递通常是个好主意:几乎每个程序都知道如何导入它们。要将我们的示例 DataFrame df
导出到 CSV 文件,使用 to_csv
方法:
In``[``100``]:``df``.``to_csv``(``"course_participants.csv"``)
如果您想将文件存储在不同的目录中,请提供完整路径作为原始字符串,例如,r"C:\path\to\desired\location\msft.csv"
。
在 Windows 上使用原始字符串处理文件路径
在字符串中,反斜杠用于转义某些字符。这就是为什么在 Windows 上处理文件路径时,您需要使用双反斜杠(
C:\\path\\to\\file.csv
)或在字符串前加上r
来将其转换为原始字符串,以字面上解释字符。这在 macOS 或 Linux 上不是问题,因为它们在路径中使用正斜杠。
通过仅提供文件名如我所做的那样,它将在与笔记本相同的目录中生成文件course_participants.csv
,内容如下:
user_id,name,age,country,score,continent 1001,Mark,55,Italy,4.5,Europe 1000,John,33,USA,6.7,America 1002,Tim,41,USA,3.9,America 1003,Jenny,12,Germany,9.0,Europe
现在您已经了解如何使用df.to_csv
方法,让我们看看如何导入 CSV 文件!
导入 CSV 文件
导入本地 CSV 文件就像将其路径提供给read_csv
函数一样简单。MSFT.csv 是我从 Yahoo! Finance 下载的 CSV 文件,包含了微软的日常历史股价 —— 你可以在伴随的存储库中的 csv 文件夹找到它:
In``[``101``]:``msft``=``pd``.``read_csv``(``"csv/MSFT.csv"``)
经常需要向read_csv
提供比仅文件名更多的参数。例如,sep
参数允许您告诉 pandas CSV 文件使用的分隔符或分隔符,以防它不是默认的逗号。在下一章中,我们将使用更多的参数,但是为了全面了解,请查看pandas 文档。
现在我们正在处理具有数千行的大型 DataFrame,通常第一步是运行info
方法以获取 DataFrame 的摘要信息。接下来,您可能希望使用head
和tail
方法查看 DataFrame 的前几行和最后几行。这些方法默认返回五行,但可以通过提供所需行数作为参数来更改。您还可以运行describe
方法获取一些基本统计信息:
In``[``102``]:``msft``.``info``()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8622 entries, 0 to 8621 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Date 8622 non-null object 1 Open 8622 non-null float64 2 High 8622 non-null float64 3 Low 8622 non-null float64 4 Close 8622 non-null float64 5 Adj Close 8622 non-null float64 6 Volume 8622 non-null int64 dtypes: float64(5), int64(1), object(1) memory usage: 471.6+ KB
In``[``103``]:``# 由于空间问题,我只选择了几列``# 您也可以直接运行:msft.head()``msft``.``loc``[:,``[``"Date"``,``"Adj Close"``,``"Volume"``]]``.``head``()
Out[103]: Date Adj Close Volume 0 1986-03-13 0.062205 1031788800 1 1986-03-14 0.064427 308160000 2 1986-03-17 0.065537 133171200 3 1986-03-18 0.063871 67766400 4 1986-03-19 0.062760 47894400
In``[``104``]:``msft``.``loc``[:,``[``"Date"``,``"Adj Close"``,``"Volume"``]]``.``tail``(``2``)
Out[104]: Date Adj Close Volume 8620 2020-05-26 181.570007 36073600 8621 2020-05-27 181.809998 39492600
In``[``105``]:``msft``.``loc``[:,``[``"Adj Close"``,``"Volume"``]]``.``describe``()
Out[105]: Adj Close Volume count 8622.000000 8.622000e+03 mean 24.921952 6.030722e+07 std 31.838096 3.877805e+07 min 0.057762 2.304000e+06 25% 2.247503 3.651632e+07 50% 18.454313 5.350380e+07 75% 25.699224 7.397560e+07 max 187.663330 1.031789e+09
Adj Close
代表调整后的收盘价格,校正了股票价格如股票拆分等公司行动。 Volume
是交易的股票数量。我总结了本章中看到的各种 DataFrame 探索方法在 表 5-8 中。
表格 5-8. DataFrame 探索方法和属性
DataFrame(df)方法/属性 | 描述 |
---|---|
df.info() |
提供数据点数量、索引类型、数据类型和内存使用情况。 |
df.describe() |
提供基本统计信息,包括计数、均值、标准差、最小值、最大值和百分位数。 |
df.head(n=5) |
返回 DataFrame 的前 n 行。 |
df.tail(n=5) |
返回 DataFrame 的最后 n 行。 |
df.dtypes |
返回每列的数据类型。 |
read_csv
函数还可以接受 URL 而不是本地 CSV 文件。以下是直接从配套仓库读取 CSV 文件的方法:
In``[``106``]:``# URL 中的换行符仅是为了使其适合页面``url``=``(``"https://raw.githubusercontent.com/fzumstein/"``"python-for-excel/1st-edition/csv/MSFT.csv"``)``msft``=``pd``.``read_csv``(``url``)
In``[``107``]:``msft``.``loc``[:,``[``"Date"``,``"Adj Close"``,``"Volume"``]]``.``head``(``2``)
Out[107]: Date Adj Close Volume 0 1986-03-13 0.062205 1031788800 1 1986-03-14 0.064427 308160000
在下一章关于时间序列的章节中,我们将继续使用此数据集和 read_csv
函数,将 Date
列转换为 DatetimeIndex
。
结论
本章充满了分析 pandas 数据集的新概念和工具。我们学会了如何加载 CSV 文件,如何处理缺失或重复数据,以及如何利用描述性统计信息。我们还看到了如何将 DataFrame 转换为交互式图表。虽然消化这些内容可能需要一些时间,但加入 pandas 工具箱后,您将很快理解其强大之处。在此过程中,我们将 pandas 与以下 Excel 功能进行了比较:
自动筛选功能
参见 “通过布尔索引进行选择”。
VLOOKUP 公式
参见 “连接和合并”。
数据透视表
参见 “数据透视和融合”。
Power Query
这是 “导入和导出数据框”、“数据操作” 和 “合并数据框” 的结合。
下一章讲述的是时间序列分析,这一功能使得 pandas 在金融行业广泛应用。让我们看看为什么 pandas 的这一部分比 Excel 更具优势!
1 pandas 1.0.0 引入了专门的
string
数据类型,以使某些操作更容易且与文本更一致。由于它仍处于实验阶段,我在本书中不打算使用它。
第六章: pandas 时间序列分析
时间序列是沿着基于时间的轴的一系列数据点,在许多不同的场景中发挥着核心作用:交易员使用历史股票价格来计算风险度量,天气预报基于传感器生成的时间序列,这些传感器测量温度、湿度和气压。数字营销部门依赖于由网页生成的时间序列,例如每小时的页面浏览量的来源和数量,并将其用于从中得出关于其营销活动的结论。
时间序列分析是数据科学家和分析师开始寻找比 Excel 更好的替代方案的主要推动力之一。以下几点总结了这一举措背后的一些原因:
大型数据集
时间序列往往会快速超出 Excel 每个工作表大约一百万行的限制。例如,如果你在 tick 数据级别上处理股票的分钟价格,你通常会处理数十万条记录——每支股票每天!
日期和时间
正如我们在第三章中所看到的,Excel 在处理日期和时间时存在各种限制,这是时间序列的基础。缺少对时区的支持以及仅限于毫秒的数字格式是其中一些限制。pandas 支持时区,并使用 NumPy 的
datetime64[ns]
数据类型,该类型可提供高达纳秒的分辨率。
缺失的功能
Excel 甚至缺乏基本工具,无法以体面的方式处理时间序列数据。例如,如果你想将每日时间序列转换为每月时间序列,尽管这是一项非常常见的任务,但却没有简单的方法来实现这一点。
DataFrames 允许你使用各种基于时间的索引:DatetimeIndex
是最常见的一个,表示一个带有时间戳的索引。其他索引类型,比如PeriodIndex
,基于时间间隔,比如小时或月份。然而,在本章中,我们只关注DatetimeIndex
,我将在接下来更详细地介绍它。
DatetimeIndex
在本节中,我们将学习如何构建一个DatetimeIndex
,如何将这样的索引筛选到特定的时间范围,并如何处理时区。
创建一个 DatetimeIndex
要构建一个DatetimeIndex
,pandas 提供了date_range
函数。它接受一个开始日期、一个频率,以及要么周期数,要么结束日期:
In``[``1``]:``# 让我们从导入本章中使用的包开始``# 并将绘图后端设置为 Plotly``import``pandas``as``pd``import``numpy``as``np``pd``.``options``.``plotting``.``backend``=``"plotly"
In``[``2``]:``# 这将根据开始时间戳、周期数和频率("D" = daily)创建一个 DatetimeIndex。``daily_index``=``pd``.``date_range``(``"2020-02-28"``,``periods``=``4``,``freq``=``"D"``)``daily_index
Out[2]: DatetimeIndex(['2020-02-28', '2020-02-29', '2020-03-01', '2020-03-02'], dtype='datetime64[ns]', freq='D')
In``[``3``]:``# 根据起始/结束时间戳创建 DatetimeIndex。频率设置为每周日一次 ("W-SUN")。``weekly_index``=``pd``.``date_range``(``"2020-01-01"``,``"2020-01-31"``,``freq``=``"W-SUN"``)``weekly_index
Out[3]: DatetimeIndex(['2020-01-05', '2020-01-12', '2020-01-19', '2020-01-26'], dtype='datetime64[ns]', freq='W-SUN')
In``[``4``]:``# 基于 weekly_index 构建 DataFrame。这可以是只在星期日开放的博物馆的访客计数。``pd``.``DataFrame``(``data``=``[``21``,``15``,``33``,``34``],``columns``=``[``"visitors"``],``index``=``weekly_index``)
Out[4]: visitors 2020-01-05 21 2020-01-12 15 2020-01-19 33 2020-01-26 34
现在让我们回到上一章的微软股票时间序列。当您仔细查看列的数据类型时,您会注意到Date
列的类型是object
,这意味着 pandas 将时间戳解释为字符串:
In``[``5``]:``msft``=``pd``.``read_csv``(``"csv/MSFT.csv"``)
In``[``6``]:``msft``.``info``()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8622 entries, 0 to 8621 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Date 8622 non-null object 1 Open 8622 non-null float64 2 High 8622 non-null float64 3 Low 8622 non-null float64 4 Close 8622 non-null float64 5 Adj Close 8622 non-null float64 6 Volume 8622 non-null int64 dtypes: float64(5), int64(1), object(1) memory usage: 471.6+ KB
有两种方法可以修复这个问题并将其转换为datetime
数据类型。第一种方法是在该列上运行to_datetime
函数。如果您希望在源数据框中更改它,请确保将转换后的列重新赋值给原始 DataFrame:
In``[``7``]:``msft``.``loc``[:,``"Date"``]``=``pd``.``to_datetime``(``msft``[``"Date"``])
In``[``8``]:``msft``.``dtypes
Out[8]: Date datetime64[ns] Open float64 High float64 Low float64 Close float64 Adj Close float64 Volume int64 dtype: object
另一种可能性是告诉read_csv
哪些列包含时间戳,使用parse_dates
参数。parse_dates
需要一个列名或索引的列表。此外,您几乎总是希望将时间戳转换为 DataFrame 的索引,因为这将使您能够轻松地过滤数据,稍后我们将看到。为了避免额外的set_index
调用,请通过index_col
参数提供您希望用作索引的列名或索引:
In``[``9``]:``msft``=``pd``.``read_csv``(``"csv/MSFT.csv"``,``index_col``=``"Date"``,``parse_dates``=``[``"Date"``])
In``[``10``]:``msft``.``info``()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 8622 entries, 1986-03-13 to 2020-05-27 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Open 8622 non-null float64 1 High 8622 non-null float64 2 Low 8622 non-null float64 3 Close 8622 non-null float64 4 Adj Close 8622 non-null float64 5 Volume 8622 non-null int64 dtypes: float64(5), int64(1) memory usage: 471.5 KB
正如info
所显示的,你现在正在处理一个具有DatetimeIndex
的 DataFrame。如果你需要更改另一个数据类型(比如你想要Volume
是float
而不是int
),你有两个选项:要么将dtype={"Volume": float}
作为参数提供给read_csv
函数,要么像下面这样应用astype
方法:
In``[``11``]:``msft``.``loc``[:,``"Volume"``]``=``msft``[``"Volume"``]``.``astype``(``"float"``)``msft``[``"Volume"``]``.``dtype
Out[11]: dtype('float64')
在处理时间序列时,在开始分析之前,确保索引已经正确排序是个好主意:
In``[``12``]:``msft``=``msft``.``sort_index``()
最后,如果你需要访问DatetimeIndex
的部分,比如只需要日期部分而不需要时间,可以像这样访问date
属性:
In``[``13``]:``msft``.``index``.``date
Out[13]: array([datetime.date(1986, 3, 13), datetime.date(1986, 3, 14), datetime.date(1986, 3, 17), ..., datetime.date(2020, 5, 22), datetime.date(2020, 5, 26), datetime.date(2020, 5, 27)], dtype=object)
替代date
,你也可以使用日期的部分,比如year
、month
、day
等。要访问具有数据类型datetime
的常规列上的相同功能,你需要使用dt
属性,例如df["column_name"].dt.date
。
有了排序的DatetimeIndex
,让我们看看如何筛选 DataFrame 来选择特定的时间段!
筛选DatetimeIndex
如果你的 DataFrame 具有DatetimeIndex
,可以通过使用格式为YYYY-MM-DD HH:MM:SS
的字符串在loc
中选择特定时间段的行。pandas 会将此字符串转换为切片,以涵盖整个时间段。例如,要选择 2019 年的所有行,请提供年份作为字符串,而不是数字:
In``[``14``]:``msft``.``loc``[``"2019"``,``"Adj Close"``]
Out[14]: Date 2019-01-02 99.099190 2019-01-03 95.453529 2019-01-04 99.893005 2019-01-07 100.020401 2019-01-08 100.745613 ... 2019-12-24 156.515396 2019-12-26 157.798309 2019-12-27 158.086731 2019-12-30 156.724243 2019-12-31 156.833633 Name: Adj Close, Length: 252, dtype: float64
让我们更进一步,绘制 2019 年 6 月至 2020 年 5 月之间的数据(见图表 6-1):
In``[``15``]:``msft``.``loc``[``"2019-06"``:``"2020-05"``,``"Adj Close"``]``.``plot``()
Figure 6-1. MSFT 的调整收盘价
将鼠标悬停在 Plotly 图表上以读取提示信息,并通过鼠标绘制矩形来放大。双击图表以返回默认视图。
下一节将使用调整后的收盘价来了解时区处理。
处理时区
微软在纳斯达克证券交易所上市。纳斯达克位于纽约,市场在下午 4 点关闭。要将此附加信息添加到 DataFrame 的索引中,首先通过DateOffset
将收盘小时添加到日期中,然后通过tz_localize
将正确的时区附加到时间戳中。由于收盘时间仅适用于收盘价,让我们创建一个新的 DataFrame:
In``[``16``]:``# 将时间信息添加到日期中``msft_close``=``msft``.``loc``[:,``[``"Adj Close"``]]``.``copy``()``msft_close``.``index``=``msft_close``.``index``+``pd``.``DateOffset``(``hours``=``16``)``msft_close``.``head``(``2``)
Out[16]: Adj Close Date 1986-03-13 16:00:00 0.062205 1986-03-14 16:00:00 0.064427
In``[``17``]:``# 将时间戳转换为时区感知时间``msft_close``=``msft_close``.``tz_localize``(``"America/New_York"``)``msft_close``.``head``(``2``)
Out[17]: Adj Close Date 1986-03-13 16:00:00-05:00 0.062205 1986-03-14 16:00:00-05:00 0.064427
如果您想将时间戳转换为 UTC 时区,请使用 DataFrame 方法tz_convert
。UTC 代表协调世界时,是格林威治标准时间(GMT)的继任者。请注意,根据纽约是否实行夏令时(DST),UTC 中的收盘时间会发生变化:
In``[``18``]:``msft_close``=``msft_close``.``tz_convert``(``"UTC"``)``msft_close``.``loc``[``"2020-01-02"``,``"Adj Close"``]``# 21:00 没有夏令时
Out[18]: Date 2020-01-02 21:00:00+00:00 159.737595 Name: Adj Close, dtype: float64
In``[``19``]:``msft_close``.``loc``[``"2020-05-01"``,``"Adj Close"``]``# 20:00 包含夏令时
Out[19]: Date 2020-05-01 20:00:00+00:00 174.085175 Name: Adj Close, dtype: float64
准备这样的时间序列将允许您即使时间信息缺失或以本地时区陈述,也能比较来自不同时区证券交易所的收盘价。
现在您已经了解了什么是DatetimeIndex
,让我们在下一节中尝试一些常见的时间序列操作,例如计算和比较股票表现。
常见的时间序列操作
在本节中,我将向您展示如何执行常见的时间序列分析任务,例如计算股票回报率、绘制各种股票的表现,并在热图中可视化它们的回报相关性。我们还将看到如何更改时间序列的频率以及如何计算滚动统计数据。
Shifting and Percentage Changes
在金融领域,股票的对数收益率通常被假设为正态分布。通过对数收益率,我指的是当前价格与上一个价格的比率的自然对数。为了对每日对数收益率的分布有所了解,让我们绘制一个直方图。但首先,我们需要计算对数收益率。在 Excel 中,通常使用涉及来自两行的单元格的公式,如图 6-2 所示。
图 6-2. 在 Excel 中计算对数收益率
Excel 和 Python 中的对数
Excel 使用
LN
表示自然对数,LOG
表示以 10 为底的对数。然而,Python 的 math 模块和 NumPy 使用log
表示自然对数,log10
表示以 10 为底的对数。
使用 pandas,而不是使用一个访问两个不同行的公式,你可以使用shift
方法将值向下移动一行。这样可以让你在单个行上操作,因此你的计算可以利用矢量化。shift
接受一个正数或负数,将时间序列向下或向上移动相应数量的行。让我们首先看看shift
如何工作:
In``[``20``]:``msft_close``.``head``()
Out[20]: 调整后收盘价 日期 1986-03-13 21:00:00+00:00 0.062205 1986-03-14 21:00:00+00:00 0.064427 1986-03-17 21:00:00+00:00 0.065537 1986-03-18 21:00:00+00:00 0.063871 1986-03-19 21:00:00+00:00 0.062760
In``[``21``]:``msft_close``.``shift``(``1``)``.``head``()
Out[21]: 调整后收盘价 日期 1986-03-13 21:00:00+00:00 NaN 1986-03-14 21:00:00+00:00 0.062205 1986-03-17 21:00:00+00:00 0.064427 1986-03-18 21:00:00+00:00 0.065537 1986-03-19 21:00:00+00:00 0.063871
现在你可以编写一个简单的基于向量的公式,易于阅读和理解。要获取自然对数,请使用 NumPy 的log
ufunc,它应用于每个元素。然后我们可以绘制直方图(见图 6-3):
In``[``22``]:``returns``=``np``.``log``(``msft_close``/``msft_close``.``shift``(``1``))``returns``=``returns``.``rename``(``columns``=``{``"Adj Close"``:``"returns"``})``returns``.``head``()
Out[22]: returns 日期 1986-03-13 21:00:00+00:00 NaN 1986-03-14 21:00:00+00:00 0.035097 1986-03-17 21:00:00+00:00 0.017082 1986-03-18 21:00:00+00:00 -0.025749 1986-03-19 21:00:00+00:00 -0.017547
In``[``23``]:``# 用每日对数收益率绘制直方图``returns``.``plot``.``hist``()
图 6-3. 直方图绘制
要获得简单的收益率,使用 pandas 内置的pct_change
方法。默认情况下,它计算与上一行的百分比变化,这也是简单收益率的定义:
In``[``24``]:``simple_rets``=``msft_close``.``pct_change``()``simple_rets``=``simple_rets``.``rename``(``columns``=``{``"Adj Close"``:``"简单回报"``})``simple_rets``.``head``()
Out[24]: 简单回报 日期 1986-03-13 21:00:00+00:00 NaN 1986-03-14 21:00:00+00:00 0.035721 1986-03-17 21:00:00+00:00 0.017229 1986-03-18 21:00:00+00:00 -0.025421 1986-03-19 21:00:00+00:00 -0.017394
到目前为止,我们只看过微软股票。在下一节中,我们将加载更多时间序列,以便查看需要多个时间序列的其他数据框方法。
重新基准化和相关性
当我们处理多个时间序列时,情况会变得稍微有趣。让我们加载亚马逊(AMZN
)、谷歌(GOOGL
)和苹果(AAPL
)的一些额外调整后的收盘价格,这些数据也是从 Yahoo! Finance 下载的。
In``[``25``]:``parts``=``[]``# 用于收集各个数据框的列表``for``ticker``in``[``"AAPL"``,``"AMZN"``,``"GOOGL"``,``"MSFT"``]:``#
usecols参数允许我们只读取日期和调整后的收盘价``adj_close``=``pd``.``read_csv``(``f``"csv/{ticker}.csv"``,``index_col``=``"Date"``,``parse_dates``=``[``"Date"``],``usecols``=``[``"Date"``,``"Adj Close"``])``# 将列名改为股票代号``adj_close``=``adj_close``.``rename``(``columns``=``{``"Adj Close"``:``ticker``})``# 将该股票的数据框附加到 parts 列表``parts``.``append``(``adj_close``)
In``[``26``]:``# 将这 4 个数据框合并为一个数据框``adj_close``=``pd``.``concat``(``parts``,``axis``=``1``)``adj_close
Out[26]: AAPL AMZN GOOGL MSFT 日期 1980-12-12 0.405683 NaN NaN NaN 1980-12-15 0.384517 NaN NaN NaN 1980-12-16 0.356296 NaN NaN NaN 1980-12-17 0.365115 NaN NaN NaN 1980-12-18 0.375698 NaN NaN NaN ... ... ... ... ... 2020-05-22 318.890015 2436.879883 1413.239990 183.509995 2020-05-26 316.730011 2421.860107 1421.369995 181.570007 2020-05-27 318.109985 2410.389893 1420.280029 181.809998 2020-05-28 318.250000 2401.100098 1418.239990 NaN 2020-05-29 317.940002 2442.370117 1433.520020 NaN [9950 行 x 4 列]
你看到concat
的威力了吗?pandas 自动对齐了各个时间序列的日期。这就是为什么在那些不如 Apple 的股票中你会得到NaN
值的原因。而且由于MSFT
在最近日期有NaN
值,你可能已经猜到我比其他股票提前两天下载了 MSFT.csv 文件。通过日期对齐时间序列是一种典型的操作,在 Excel 中非常繁琐且容易出错。删除所有包含缺失值的行将确保所有股票具有相同的数据点:
In``[``27``]:
adj_close=``adj_close``.``dropna``()``adj_close``.``info``()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 3970 entries, 2004-08-19 to 2020-05-27 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 AAPL 3970 non-null float64 1 AMZN 3970 non-null float64 2 GOOGL 3970 non-null float64 3 MSFT 3970 non-null float64 dtypes: float64(4) memory usage: 155.1 KB
现在我们将价格重新基准化,使所有时间序列从 100 开始。这样可以在图表中比较它们的相对表现;见图 6-4。要重新基准化时间序列,将每个值除以其起始值并乘以 100,这是新的基准。如果在 Excel 中进行此操作,通常会编写一个结合绝对和相对单元格引用的公式,然后将该公式复制到每行和每个时间序列。在 pandas 中,由于矢量化和广播技术,你只需处理一个公式:
In``[``28``]:
# 使用 2019 年 6 月至 2020 年 5 月的样本adj_close_sample
=adj_close
.loc
["2019-06"
:"2020-05"
,:]
rebased_prices=``adj_close_sample``/``adj_close_sample``.``iloc``[``0``,``:]``*``100``rebased_prices``.``head``(``2``)
Out[28]: AAPL AMZN GOOGL MSFT Date 2019-06-03 100.000000 100.000000 100.00000 100.000000 2019-06-04 103.658406 102.178197 101.51626 102.770372
In``[``29``]:
rebased_prices.
plot``()
图 6-4. 重新基准化的时间序列
要查看不同股票的回报独立性,可以使用corr
方法查看它们的相关性。不幸的是,pandas 没有提供内置的绘制热力图形式的相关矩阵的方法,因此我们需要直接通过其plotly.express
接口使用 Plotly(见图 6-5):
In``[``30``]:
# 每日对数收益率的相关性returns
=np
.log
(adj_close
/adj_close
.shift
(1
))returns
.corr
()
Out[30]: AAPL AMZN GOOGL MSFT AAPL 1.000000 0.424910 0.503497 0.486065 AMZN 0.424910 1.000000 0.486690 0.485725 GOOGL 0.503497 0.486690 1.000000 0.525645 MSFT 0.486065 0.485725 0.525645 1.000000
In``[``31``]:
importplotly.express
as`px
In``[``32``]:``fig``=``px``.``imshow``(``returns``.``corr``(),``x``=``adj_close``.``columns``,``y``=``adj_close``.``columns``,``color_continuous_scale``=``list``(``reversed``(``px``.``colors``.``sequential``.``RdBu``)),``zmin``=-``1``,``zmax``=``1``)``fig``.``show``()
如果你想详细了解imshow
的工作原理,请查看Plotly Express API 文档。
图 6-5. 相关性热力图
到目前为止,我们已经学到了关于时间序列的许多知识,包括如何组合和清理它们,如何计算收益率和相关性。但是,如果你决定日收益率不适合你的分析基础,想要月度收益率呢?如何改变时间序列数据的频率将成为下一节的主题。
重新采样
时间序列的常见任务包括上采样和下采样。上采样意味着将时间序列转换为频率更高的序列,而下采样则意味着将其转换为频率较低的序列。例如,在财务报表中,通常显示月度或季度表现。要将日度时间序列转换为月度时间序列,请使用resample
方法,该方法接受频率字符串如M
表示月末或BM
表示工作日结束。你可以在pandas 文档中找到所有频率字符串的列表。类似于groupby
的工作方式,然后链接定义如何重新采样的方法。我使用last
来始终获取该月的最后观测值:
In``[``33``]:``end_of_month``=``adj_close``.``resample``(``"M"``)``.``last``()``end_of_month``.``head``()
Out[33]: AAPL AMZN GOOGL MSFT Date 2004-08-31 2.132708 38.139999 51.236237 17.673630 2004-09-30 2.396127 40.860001 64.864868 17.900215 2004-10-31 3.240182 34.130001 95.415413 18.107374 2004-11-30 4.146072 39.680000 91.081078 19.344421 2004-12-31 3.982207 44.290001 96.491493 19.279480
你可以选择除last
外的任何在groupby
上有效的方法,如sum
或mean
。还有ohlc
,它方便地返回该期间的开盘价、最高价、最低价和收盘价。这可能用作创建股票价格常用的蜡烛图表的源。
如果你只有那些月末时间序列数据,并且需要生成周度时间序列,你需要对时间序列进行上采样。通过使用asfreq
,你告诉 pandas 不要应用任何转换,因此你将看到大多数值显示为NaN
。如果你想要向前填充最后已知值,使用ffill
方法:
In``[``34``]:``end_of_month``.``resample``(``"D"``)``.``asfreq``()``.``head``()``# No transformation
Out[34]: AAPL AMZN GOOGL MSFT 日期 2004-08-31 2.132708 38.139999 51.236237 17.67363 2004-09-01 NaN NaN NaN NaN 2004-09-02 NaN NaN NaN NaN 2004-09-03 NaN NaN NaN NaN 2004-09-04 NaN NaN NaN NaN
`In
[
35]:
end_of_month.
resample(
"W-FRI")
.ffill
().
head()
# 前向填充
Out[35]: AAPL AMZN GOOGL MSFT 日期 2004-09-03 2.132708 38.139999 51.236237 17.673630 2004-09-10 2.132708 38.139999 51.236237 17.673630 2004-09-17 2.132708 38.139999 51.236237 17.673630 2004-09-24 2.132708 38.139999 51.236237 17.673630 2004-10-01 2.396127 40.860001 64.864868 17.900215
降采样数据是平滑时间序列的一种方式。计算滚动窗口中的统计数据是另一种方式,我们将在下面看到。
滚动窗口
当计算时间序列统计数据时,通常需要一个滚动统计量,如移动平均线。移动平均线查看时间序列的子集(比如 25 天),并在将窗口向前移动一天之前从该子集中取平均值。这将产生一个新的时间序列,更平滑且不易受到异常值的影响。如果你从事算法交易,可能会关注移动平均线与股票价格的交点,并将其(或其变体)作为交易信号。DataFrame 具有rolling
方法,接受观察次数作为参数。然后,将其与要使用的统计方法链接起来——对于移动平均线,就是mean
。通过查看图 6-6,你可以轻松比较原始时间序列与平滑移动平均线:
In``[``36``]:``# 使用 2019 年数据为 MSFT 绘制移动平均线``msft19``=``msft``.``loc``[``"2019"``,``[``"Adj Close"``]]``.``copy``()``# 将 25 天移动平均线作为新列添加到 DataFrame``msft19``.``loc``[:,``"25day average"``]``=``msft19``[``"Adj Close"``]``.``rolling``(``25``)``.``mean``()``msft19``.``plot``()
图 6-6. 移动平均线图
你可以使用许多其他统计方法来替代mean
,包括count
、sum
、median
、min
、max
、std
(标准差)或var
(方差)。
到目前为止,我们已经看到了 pandas 最重要的功能。同样重要的是要理解 pandas 的局限性,即使它们可能现在仍然很远。
pandas 的局限性
当你的 DataFrame 开始变得更大时,了解 DataFrame 能容纳的上限是个好主意。与 Excel 不同,Excel 的每个工作表都有大约一百万行和一万二千列的硬性限制,而 pandas 只有软性限制:所有数据必须适合计算机可用的内存。如果情况不是这样,可能有一些简单的解决方案:只加载你需要的数据集中的那些列,或者删除中间结果以释放一些内存。如果这些方法都不起作用,有一些项目可能会让 pandas 用户感到熟悉,但可以处理大数据。其中一个项目是Dask,它在 NumPy 和 pandas 之上工作,允许你通过将数据拆分成多个 pandas DataFrame,并将工作负载分配到多个 CPU 核心或机器上来处理大型数据集。其他与某种 DataFrame 类似的大数据项目包括Modin,Koalas,Vaex,PySpark,cuDF,Ibis和PyArrow。我们将在下一章简要介绍 Modin,但除此之外,在本书中我们不会进一步探索这个主题。
结论
时间序列分析是我认为 Excel 落后最多的领域,所以在阅读本章之后,你可能会明白为什么 pandas 在金融领域如此成功,金融行业严重依赖时间序列。我们已经看到了处理时区、重采样时间序列或生成相关矩阵是多么容易,而这些功能在 Excel 中要么不支持,要么需要繁琐的解决方案。
然而,了解如何使用 pandas 并不意味着你必须放弃 Excel,因为这两个世界可以非常好地配合:pandas DataFrame 是在两个世界之间传输数据的好方法,正如我们将在下一部分中看到的,该部分将介绍绕过 Excel 应用程序完全读写 Excel 文件的方式。这非常有帮助,因为它意味着你可以在 Python 支持的所有操作系统上使用 Python 操作 Excel 文件,包括 Linux。为了开始这段旅程,下一章将向你展示如何使用 pandas 来自动化繁琐的手动流程,比如将 Excel 文件聚合成摘要报告。
第三部分:无需使用 Excel 读写 Excel 文件
第七章:使用 pandas 进行 Excel 文件操作
在经历了六章对工具、Python 和 pandas 的激烈介绍之后,我将给你一个休息时间,并从一个实际案例研究开始本章,让你能够将你新获得的技能应用到实际中:仅需十行 pandas 代码,你就可以将数十个 Excel 文件合并成一份 Excel 报告,准备发送给你的经理们。案例研究之后,我将更深入地介绍 pandas 提供的处理 Excel 文件的工具:read_excel
函数和 ExcelFile
类用于读取,to_excel
方法和 ExcelWriter
类用于写入 Excel 文件。pandas 不依赖 Excel 应用程序来读写 Excel 文件,这意味着本章中的所有代码示例都可以在 Python 运行的任何地方运行,包括 Linux。
案例研究:Excel 报告
这个案例研究受到我在过去几年参与的几个真实报告项目的启发。尽管这些项目涉及完全不同的行业—包括电信、数字营销和金融—但它们仍然非常相似:起点通常是一个包含需要处理为 Excel 报告的 Excel 文件的目录—通常是每月、每周或每日的基础上。在伴随的存储库中,在 sales_data 目录中,你会找到用于电信供应商销售不同套餐(Bronze、Silver、Gold)的虚构销售交易的 Excel 文件,这些套餐分布在美国的几家商店中。对于每个月,都有两个文件,一个在 new 子文件夹中用于新合同,另一个在 existing 子文件夹中用于现有客户。由于这些报告来自不同的系统,它们有不同的格式:新客户以 xlsx 文件的形式交付,而现有客户则以旧的 xls 格式交付。每个文件最多有 10,000 笔交易,我们的目标是生成一个 Excel 报告,显示每个商店和月份的总销售额。要开始,请查看 new 子文件夹中的 January.xlsx 文件中的 Figure 7-1。
图 7-1. January.xlsx 的前几行
existing 子文件夹中的 Excel 文件看起来几乎相同,只是缺少 status
列,并存储在传统的 xls 格式中。作为第一步,让我们使用 pandas 的 read_excel
函数读取 1 月份的新交易:
In``[``1``]:``import``pandas``as``pd
`In
[
2]:
df=
pd.
read_excel(
"sales_data/new/January.xlsx")
df.
info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 9493 entries, 0 to 9492 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 transaction_id 9493 non-null object 1 store 9493 non-null object 2 status 9493 non-null object 3 transaction_date 9493 non-null datetime64[ns] 4 plan 9493 non-null object 5 contract_type 9493 non-null object 6 amount 9493 non-null float64 dtypes: datetime64ns** float64(1), object(5) memory usage: 519.3+ KB
使用 Python 3.9 的 READ_EXCEL 函数
这与第 5 章中的警告相同:如果您使用 Python 3.9 或更高版本运行
pd.read_excel
,请确保至少使用 pandas 1.2,否则在读取 xlsx 文件时会出错。
如您所见,pandas 已正确识别了所有列的数据类型,包括transaction_date
的日期格式。这使我们能够在不需要进一步准备数据的情况下处理数据。由于此示例故意简单,我们可以继续创建一个名为 sales_report_pandas.py 的短脚本,如示例 7-1 所示。此脚本将从两个目录中读取所有 Excel 文件,聚合数据,并将汇总表写入新的 Excel 文件中。使用 VS Code 自己编写脚本,或者从伴随的代码库中打开它。如需了解如何在 VS Code 中创建或打开文件,请再次参考第 2 章。如果您自己创建,请确保将其放在 sales_data 文件夹旁边——这样可以在无需调整任何文件路径的情况下运行脚本。
示例 7-1. sales_report_pandas.py
from``pathlib``import``Path``import``pandas``as``pd``# 本文件的目录``this_dir``=``Path``(``__file__``)``.``resolve``()``.``parent
# 从 sales_data 的所有子文件夹中读取所有 Excel 文件``parts``=``[]``for``path``in``(``this_dir``/``"sales_data"``)``.``rglob``(``"*.xls*"``):
print``(``f``'读取 {path.name}'``)``part``=``pd``.``read_excel``(``path``,``index_col``=``"transaction_id"``)``parts``.``append``(``part``)``# 将每个文件的 DataFrame 合并为单个 DataFrame``# pandas 会正确对齐列``df``=``pd``.``concat``(``parts``)``# 将每个商店转换为列,并按日期汇总所有交易``pivot``=``pd``.``pivot_table``(``df``,``index``=``"transaction_date"``,``columns``=``"store"``,``values``=``"amount"``,``aggfunc``=``"sum"``)``# 重新采样到月底并分配索引名称``summary``=``pivot``.``resample``(``"M"``)``.``sum``()``summary``.``index``.``name``=``"Month"``# 将汇总报告写入 Excel 文件``summary``.``to_excel``(``this_dir``/``"sales_report_pandas.xlsx"``)
在本章之前,我一直使用字符串来指定文件路径。通过使用标准库的 pathlib 模块中的
Path
类,你可以获得一套强大的工具:路径对象使您能够通过连接个别部分使用斜杠来轻松构建路径,正如下面四行代码中使用this_dir / "sales_data"
一样。这些路径适用于各种平台,并允许您像在下一点解释的那样使用rglob
等过滤器。当您运行时,__file__
将解析为源代码文件的路径——使用其parent
将因此给出此文件所在目录的名称。我们在调用parent
之前使用的resolve
方法将路径转换为绝对路径。如果您在 Jupyter 笔记本中运行此代码,则必须使用this_dir = Path(".").resolve()
替换此行,其中点表示当前目录。在大多数情况下,接受字符串形式路径的函数和类也接受路径对象。
从特定目录递归读取所有 Excel 文件的最简单方法是使用路径对象的
rglob
方法。glob
是通配符扩展的简写,用于使用通配符进行路径名扩展。?
通配符代表一个字符,而*
代表任意数量的字符(包括零个)。rglob
中的 r 表示递归通配符,即它将在所有子目录中查找匹配的文件——相应地,glob
将忽略子目录。使用*.xls*
作为通配符表达式确保可以找到旧的和新的 Excel 文件,因为它匹配.xls
和.xlsx
。通常建议稍微增强表达式,例如[!~$]*.xls*
。这会忽略临时 Excel 文件(它们的文件名以~$
开头)。有关如何在 Python 中使用通配符扩展的更多背景信息,请参阅 Python 文档。
运行脚本,例如,通过点击 VS Code 右上角的“运行文件”按钮。脚本将需要一段时间来完成,完成后,Excel 工作簿 sales_report_pandas.xlsx 将出现在与脚本相同的目录中。Sheet1 的内容应该像 图 7-2 中所示。这对于只有十行代码的结果相当令人印象深刻——即使您需要调整第一列的宽度以查看日期!
图 7-2. sales_report_pandas.xlsx(原样,不调整任何列宽)
对于像这样简单的情况,pandas 提供了处理 Excel 文件的非常简便的解决方案。但是,我们可以做得更好——毕竟,一个标题、一些格式(包括列宽和一致的小数位数)以及一个图表都不会有什么坏处。这正是我们将在下一章中通过直接使用 pandas 底层使用的写入库来解决的问题。然而,在我们到达那里之前,让我们更详细地看看如何使用 pandas 读取和写入 Excel 文件。
使用 pandas 读写 Excel 文件
该案例研究使用 read_excel
和 to_excel
的默认参数来简化操作。在本节中,我将介绍使用 pandas 读写 Excel 文件时最常用的参数和选项。我们将从 read_excel
函数和 ExcelFile
类开始,然后再看看 to_excel
方法和 ExcelWriter
类。在此过程中,我还会介绍 Python 的 with
语句。
read_excel
函数和 ExcelFile 类
该案例研究使用的是 Excel 工作簿,数据方便地位于第一个工作表的 A1 单元格。实际情况下,您的 Excel 文件可能没有那么好组织。在这种情况下,pandas 提供了参数来优化读取过程。接下来的示例中,我们将使用 companion 存储库的 xl 文件夹中的 stores.xlsx 文件。第一个工作表显示在 Figure 7-3 中。
图 7-3. stores.xlsx 的第一个工作表
通过使用 sheet_name
、skiprows
和 usecols
参数,我们可以告诉 pandas 我们想要读取的单元格范围。通常情况下,通过运行 info
方法查看返回的 DataFrame 的数据类型是个好主意:
In``[``3``]:``df``=``pd``.``read_excel``(``"xl/stores.xlsx"``,``sheet_name``=``"2019"``,``skiprows``=``1``,``usecols``=``"B:F"``)``df
Out[3]: Store Employees Manager Since Flagship 0 New York 10 Sarah 2018-07-20 False 1 San Francisco 12 Neriah 2019-11-02 MISSING 2 Chicago 4 Katelin 2020-01-31 NaN 3 Boston 5 Georgiana 2017-04-01 True 4 Washington DC 3 Evan NaT False 5 Las Vegas 11 Paul 2020-01-06 False
`In
[
4]:
df.
info``()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 6 entries, 0 to 5 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Store 6 non-null object 1 Employees 6 non-null int64 2 Manager 6 non-null object 3 Since 5 non-null datetime64[ns] 4 Flagship 5 non-null object dtypes: datetime64ns** int64(1), object(3) memory usage: 368.0+ bytes
除了 Flagship
列之外,一切看起来都很好——它的数据类型应为 bool
而不是 object
。为了修复这个问题,我们可以提供一个转换函数,处理该列中的问题单元格(而不是编写 fix_missing
函数,我们也可以使用 lambda 表达式代替):
In``[``5``]:``def``fix_missing``(``x``):``return``False``if``x``in``[``""``,``"MISSING"``]``else``x
In``[``6``]:``df``=``pd``.``read_excel``(``"xl/stores.xlsx"``,``sheet_name``=``"2019"``,``skiprows``=``1``,``usecols``=``"B:F"``,``converters``=``{``"Flagship"``:``fix_missing``})``df
Out[6]: Store Employees Manager Since Flagship 0 纽约 10 莎拉 2018-07-20 False 1 旧金山 12 尼赖亚 2019-11-02 False 2 芝加哥 4 凯特琳 2020-01-31 False 3 波士顿 5 乔治安娜 2017-04-01 True 4 华盛顿特区 3 伊万 NaT False 5 拉斯维加斯 11 保罗 2020-01-06 False
`In
[
7]:
# Flagship 列现在的 Dtype 是 "bool"df
.info
()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 6 entries, 0 to 5 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 分店 6 non-null object 1 员工数量 6 non-null int64 2 经理 6 non-null object 3 自从 5 non-null datetime64[ns] 4 是否旗舰店 6 non-null bool dtypes: bool(1), datetime64ns** int64(1), object(2) memory usage: 326.0+ bytes
read_excel
函数还接受一个工作表名称列表。在这种情况下,它将返回一个字典,其中 DataFrame 作为值,工作表名称作为键。要读取所有工作表,您需要提供 sheet_name=None
。还请注意,我在使用 usecols
时提供了表格列名的略微变体:
In``[``8``]:``sheets``=``pd``.``read_excel``(``"xl/stores.xlsx"``,``sheet_name``=``[``"2019"``,``"2020"``],``skiprows``=``1``,``usecols``=``[``"分店"``,``"员工数量"``])``sheets``[``"2019"``]``.``head``(``2``)
Out[8]: 分店 员工数量 0 纽约 10 1 旧金山 12
如果源文件没有列标题,请将 header=None
设置,并通过 names
提供它们。请注意,sheet_name
还接受工作表索引:
In``[``9``]:``df``=``pd``.``read_excel``(``"xl/stores.xlsx"``,``sheet_name``=``0``,``skiprows``=``2``,``skipfooter``=``3``,``usecols``=``"B:C,F"``,``header``=``None``,``names``=``[``"分店"``,``"员工数量"``,``"是否旗舰店"``])``df
Out[9]: 分店 员工数量 是否旗舰店 0 纽约 10 False 1 旧金山 12 MISSING 2 芝加哥 4 NaN
要处理 NaN
值,请使用 na_values
和 keep_default_na
的组合。下一个示例告诉 pandas 仅将包含 MISSING
字样的单元格解释为 NaN
,而不是其他任何内容:
In``[``10``]:``df``=``pd``.``read_excel``(``"xl/stores.xlsx"``,``sheet_name``=``"2019"``,``skiprows``=``1``,``usecols``=``"B,C,F"``,``skipfooter``=``2``,``na_values``=``"MISSING"``,``keep_default_na``=``False``)``df
Out[10]: 分店 员工数量 是否旗舰店 0 纽约 10 False 1 旧金山 12 NaN 2 芝加哥 4 3 波士顿 5 True
pandas 提供了另一种通过使用ExcelFile
类读取 Excel 文件的方法。这主要在您想要从遗留 xls 格式文件中读取多个工作表时有所不同:在这种情况下,使用ExcelFile
将更快,因为它防止 pandas 多次读取整个文件。ExcelFile
可以作为上下文管理器使用(参见侧边栏),因此文件将再次被正确关闭。
上下文管理器和
with
语句首先,Python 中的
with
语句与 VBA 中的With
语句没有任何关系:在 VBA 中,它用于在同一对象上运行一系列语句,而在 Python 中,它用于管理文件或数据库连接等资源。如果要加载最新的销售数据以便进行分析,可能需要打开文件或建立到数据库的连接。在完成数据读取后,尽快关闭文件或连接是最佳实践。否则,您可能会遇到无法打开另一个文件或建立另一个数据库连接的情况——文件处理程序和数据库连接是有限资源。手动打开和关闭文本文件的方法如下(w
表示以写
模式打开文件,如果文件已存在则替换它):`In
[
11]:
f=
open(
"output.txt",
"w")
f.
write(
"Some text")
f.
close()
运行此代码将在与正在运行代码的笔记本相同的目录中创建一个名为 output.txt 的文件,并将“Some text”写入其中。要读取文件,您将使用
r
而不是w
,要在文件末尾追加内容,使用a
。由于文件也可以从程序外部进行操作,此类操作可能会失败。您可以通过使用我将在第十一章中介绍的 try/except 机制来处理此问题。然而,由于这是一个常见的操作,Python 提供了with
语句以简化操作:
In``[``12``]:``with``open``(``"output.txt"``,``"w"``)``as``f``:``f``.``write``(``"Some text"``)
当代码执行离开
with
语句的主体时,无论是否发生异常,文件都会自动关闭。这确保资源得到适当的清理。支持with
语句的对象称为上下文管理器;这包括本章中的ExcelFile
和ExcelWriter
对象,以及我们将在第十一章中查看的数据库连接对象。
让我们看看ExcelFile
类的实际应用:
In``[``13``]:``with``pd``.``ExcelFile``(``"xl/stores.xls"``)``as``f``:``df1``=``pd``.``read_excel``(``f``,``"2019"``,``skiprows``=``1``,``usecols``=``"B:F"``,``nrows``=``2``)``df2``=``pd``.``read_excel``(``f``,``"2020"``,``skiprows``=``1``,``usecols``=``"B:F"``,``nrows``=``2``)``df1
Out[13]: Store Employees Manager Since Flagship 0 纽约 10 莎拉 2018-07-20 False 1 旧金山 12 尼莉亚 2019-11-02 MISSING
ExcelFile
还允许您访问所有工作表的名称:
In``[``14``]:``stores``=``pd``.``ExcelFile``(``"xl/stores.xlsx"``)``stores``.``sheet_names
Out[14]: ['2019', '2020', '2019-2020']
最后,pandas 允许您从 URL 直接读取 Excel 文件,类似于我们在第五章中读取 CSV 文件的方法。让我们直接从伴随存储库中读取:
In``[``15``]:``url``=``(``"https://raw.githubusercontent.com/fzumstein/"``"python-for-excel/1st-edition/xl/stores.xlsx"``)``pd``.``read_excel``(``url``,``skiprows``=``1``,``usecols``=``"B:E"``,``nrows``=``2``)
Out[15]: Store Employees Manager Since 0 纽约 10 莎拉 2018-07-20 1 旧金山 12 尼莉亚 2019-11-02
通过 pandas 读取 XLSB 文件
如果你使用版本低于 1.3 的 pandas,读取 xlsb 文件需要在
read_excel
函数或ExcelFile
类中显式指定引擎:
pd``.``read_excel``(``"xl/stores.xlsb"``,``engine``=``"pyxlsb"``)
这需要安装
pyxlsb
包,因为它不是 Anaconda 的一部分——我们将在下一章节介绍其它引擎时详细讨论。
总结一下,表格 7-1 展示了最常用的read_excel
参数。你可以在官方文档中找到完整列表。
Table 7-1. read_excel
的选定参数
参数 | 描述 |
---|---|
sheet_name |
你可以提供一个零基索引的表名,例如,sheet_name=0 。如果你设置sheet_name=None ,pandas 将读取整个工作簿,并返回形如{"sheetname": df} 的字典。如果要读取选择的几个表,可以提供一个包含表名或索引的列表。 |
skiprows |
这允许您跳过指定数量的行。 |
usecols |
如果 Excel 文件包含列标题的名称,请以列表形式提供它们来选择列,例如,["Store", "Employees"] 。或者,可以是列索引的列表,例如,[1, 2] ,或者一个包含 Excel 列名(包括范围)的字符串,例如,"B:D,G" 。还可以提供一个函数:例如,只包含以Manager 开头的列,请使用:usecols=lambda x: x.startswith("Manager") 。 |
nrows |
你要读取的行数。 |
index_col |
指示应该作为索引的列,可以是列名或索引,例如,index_col=0 。如果提供多列的列表,将创建一个分层索引。 |
header |
如果设置header=None ,则分配默认整数标题,除非通过names 参数提供所需的名称。如果提供索引列表,将创建分层列标题。 |
names |
以列表形式提供您的列的期望名称。 |
na_values |
默认情况下,pandas 将以下单元格值解释为NaN (我在第五章介绍了NaN ):空单元格、#NA 、NA 、null 、#N/A 、N/A 、NaN 、n/a 、-NaN 、1.#IND 、nan 、#N/A N/A 、-1.#QNAN 、-nan 、NULL 、-1.#IND 、<NA> 、1.#QNAN 。如果您想要添加一个或多个值到该列表,请通过na_values 提供它们。 |
keep_default_na |
如果您想忽略 pandas 解释为NaN 的默认值,请设置keep_default_na=False 。 |
convert_float |
Excel 将所有数字内部存储为浮点数,并且默认情况下,pandas 会将没有意义小数的数字转换为整数。如果您想改变这种行为,请设置convert_float=False (这可能会稍快一些)。 |
converters |
允许您为每列提供一个函数以转换其值。例如,要使某一列中的文本变为大写,使用以下内容:converters={"column_name": lambda x: x.upper()} 。 |
读取 Excel 文件与 pandas 相关的内容就这么多,接下来我们转向另一方面,学习如何在下一节中写入 Excel 文件!
to_excel 方法和 ExcelWriter 类
使用 pandas 写入 Excel 文件的最简单方法是使用 DataFrame 的to_excel
方法。它允许您指定要将 DataFrame 写入的哪个工作表的哪个单元格。您还可以决定是否包括 DataFrame 的列标题和索引,以及如何处理 Excel 中没有等价表示的数据类型,如np.nan
和np.inf
。让我们从创建具有不同数据类型的 DataFrame 开始,并使用其to_excel
方法:
In``[``16``]:``import``numpy``as``np``import``datetime``as``dt
`In
[
17]:
data=
[[dt
.datetime
(2020
,1
,1
,10
,13
),2.222
,1
,True
],[
dt.
datetime(
2020,
1,
2),
np.
nan,
2,
False],
[dt
.datetime
(2020
,1
,2
),np
.inf
,3
,True
]]df
=pd
.DataFrame
(data
=data
,columns
=[
"Dates",
"Floats",
"Integers",
"Booleans"])
df.
index.
name=
"index"``df
Out[17]: 日期 浮点数 整数 布尔值 索引 0 2020-01-01 10:13:00 2.222 1 True 1 2020-01-02 00:00:00 NaN 2 False 2 2020-01-02 00:00:00 inf 3 True
In``[``18``]:``df``.``to_excel``(``"written_with_pandas.xlsx"``,``sheet_name``=``"Output"``,``startrow``=``1``,``startcol``=``1``,``index``=``True``,``header``=``True``,``na_rep``=``"<NA>"``,``inf_rep``=``"<INF>"``)
运行 to_excel
命令将创建 Excel 文件,如 Figure 7-4 所示(您需要扩展列 C
以正确查看日期)。
图 7-4. written_with_pandas.xlsx
如果要将多个 DataFrame 写入同一张或不同的工作表中,需要使用 ExcelWriter
类。以下示例将相同的 DataFrame 写入 Sheet1 的两个不同位置,并再次写入 Sheet2:
`In
[
19]:
使用pd
.ExcelWriter
("written_with_pandas2.xlsx"
)作为
writer:
df.
to_excel(
writer,
sheet_name=
"Sheet1",
startrow=
1,
startcol=
1)
df.
to_excel(
writer,
sheet_name=
"Sheet1",
startrow=
10,
startcol=
1)
df.
to_excel(
writer,
sheet_name=
"Sheet2")
由于我们使用 ExcelWriter
类作为上下文管理器,当退出上下文管理器时(即缩进停止时),文件会自动写入磁盘。否则,您需要显式调用 writer.save()
。要获取 to_excel
接受的最常用参数摘要,请参阅 Table 7-2。您可以在 官方文档 中找到所有参数的完整列表。
表 7-2. to_excel 的选定参数
参数 | 描述 |
---|---|
sheet_name |
要写入的工作表名称。 |
startrow 和 startcol |
startrow 是 DataFrame 将写入的第一行,startcol 是第一列。这使用从零开始的索引,因此如果要将 DataFrame 写入到 B3 单元格,使用 startrow=2 和 startcol=1 。 |
index 和 header |
如果要隐藏索引和/或标题,将它们分别设置为 index=False 和 header=False 。 |
na_rep 和 inf_rep |
默认情况下,np.nan 将转换为空单元格,而 np.inf (NumPy 表示的无穷大)将转换为字符串 inf 。提供值可以更改此行为。 |
freeze_panes |
通过提供一个元组来冻结前几行和列:例如 (2, 1) 将冻结前两行和第一列。 |
如您所见,使用 pandas 轻松读写简单的 Excel 文件效果很好。然而,有一些限制——让我们来看看是哪些限制!
使用 pandas 处理 Excel 文件的限制
使用 pandas 接口读写 Excel 文件在简单情况下效果很好,但也有一些限制:
-
当将 DataFrame 写入文件时,无法包含标题或图表。
-
-
在 Excel 中无法更改默认的标题和索引格式。
-
-
在读取文件时,pandas 自动将带有诸如
#REF!
或#NUM!
的错误单元格转换为NaN
,这使得在电子表格中搜索特定错误变得不可能。 -
-
处理大型 Excel 文件可能需要额外的设置,通过直接使用读取器和写入器包更容易控制,我们将在下一章中讨论。
结论
pandas 的好处在于,它提供了一个一致的接口来处理所有支持的 Excel 文件格式,无论是 xls、xlsx、xlsm 还是 xlsb。这使得我们能够轻松读取一个目录中的 Excel 文件,聚合数据,并将摘要信息仅用十行代码转储到 Excel 报告中。
pandas,然而,并不是自己完成繁重的工作:在幕后,它选择一个读取器或写入器包来完成工作。在下一章中,我将向你展示 pandas 使用哪些读取器和写入器包,并且如何直接或与 pandas 结合使用它们。这将使我们能够解决我们在前一节中看到的限制。
第八章:使用读取器和写入器包进行 Excel 文件操作
本章介绍了 OpenPyXL、XlsxWriter、pyxlsb、xlrd 和 xlwt:这些是可以读写 Excel 文件的包,并且在调用 read_excel
或 to_excel
函数时被 pandas 底层使用。直接使用读取器和写入器包可以让您创建更复杂的 Excel 报告,并且可以微调读取过程。此外,如果您曾经需要在一个只需要读写 Excel 文件而不需要其他 pandas 功能的项目中工作,安装完整的 NumPy/pandas 堆栈可能会有些过火。我们将从学习何时使用哪个包以及它们的语法工作开始本章,然后再讨论一些高级主题,包括如何处理大型 Excel 文件以及如何将 pandas 与读取器和写入器包结合起来以改进 DataFrame 的样式。最后,我们将再次回顾上一章开头的案例研究,并通过格式化表格和添加图表来增强 Excel 报告。与上一章类似,本章不需要安装 Excel,这意味着所有的代码示例都可以在 Windows、macOS 和 Linux 上运行。
读取器和写入器包
读取器和写入器的使用场景可能有些令人不知所措:本节将介绍不少于六个包,因为几乎每种 Excel 文件类型都需要不同的包。每个包使用不同的语法,通常与原始的 Excel 对象模型有很大不同,这并不让人更容易——我将在下一章中更多地讨论 Excel 对象模型。这意味着即使您是经验丰富的 VBA 开发人员,您也可能需要查找很多命令。本节从概述何时需要使用哪个包开始,然后介绍一个辅助模块,使使用这些包变得稍微容易一些。之后,它以食谱的形式呈现每个包,您可以查阅最常用命令的工作方式。
何时使用哪个包
本节介绍了以下六个用于读取、写入和编辑 Excel 文件的包:
要了解哪个包可以做什么,请查看表 8-1。例如,要读取 xlsx 文件格式,您将需要使用 OpenPyXL 包:
表 8-1. 何时使用哪个包
Excel 文件格式 | 读取 | 写入 | 编辑 |
---|---|---|---|
xlsx |
OpenPyXL | OpenPyXL, XlsxWriter | OpenPyXL |
xlsm |
OpenPyXL | OpenPyXL, XlsxWriter | OpenPyXL |
xltx , xltm |
OpenPyXL | OpenPyXL | OpenPyXL |
xlsb |
pyxlsb | - | - |
xls , xlt |
xlrd | xlwt | xlutils |
如果你想写入 xlsx 或 xlsm 文件,你需要在 OpenPyXL 和 XlsxWriter 之间做出选择。这两个包都涵盖了类似的功能,但每个包可能有一些其他包没有的独特功能。由于这两个库都在积极开发中,这种情况随着时间的推移而变化。以下是它们的区别的高级概述:
-
OpenPyXL 可以读取、写入和编辑,而 XlsxWriter 只能写入。
-
-
OpenPyXL 更容易生成带有 VBA 宏的 Excel 文件。
-
-
XlsxWriter 的文档更好。
-
-
XlsxWriter 的速度比 OpenPyXL 快,但根据你写入的工作簿的大小,差异可能不显著。
-
XLWINGS 在哪里?
如果你想知道表 表 8-1 中的 xlwings 在哪里,答案是无处不在或无处不在,这取决于你的用例:与本章中的任何包不同,xlwings 依赖于 Excel 应用程序,这通常是不可用的,例如,如果你需要在 Linux 上运行你的脚本。另一方面,如果你愿意在 Windows 或 macOS 上运行你的脚本,那么你就可以访问安装的 Excel,xlwings 确实可以作为本章中所有包的替代品使用。由于 Excel 依赖性是 xlwings 与所有其他 Excel 包之间的基本差异,我会在下一章中介绍 xlwings,这一章开始了本书的 第四部分。
pandas 使用它能找到的 writer 包,如果你同时安装了 OpenPyXL 和 XlsxWriter,XlsxWriter 就是默认的。如果你想选择 pandas 应该使用哪个包,请在 read_excel
或 to_excel
函数的 engine
参数中指定,或者在 ExcelFile
和 ExcelWriter
类中分别指定。引擎是包名的小写形式,因此要使用 OpenPyXL 而不是 XlsxWriter 写文件,运行以下命令:
df``.``to_excel``(``"filename.xlsx"``,``engine``=``"openpyxl"``)
一旦你知道你需要哪个包,就会有第二个挑战在等着你:大多数这些包需要你写相当多的代码来读取或写入一系列单元格,而且每个包都使用不同的语法。为了让你的生活更轻松,我创建了一个辅助模块,我将在下面介绍。
excel.py 模块
我创建了 excel.py
模块,以便在使用读取器和写入器包时让你的生活更轻松,因为它解决了以下问题:
包交换
不得不切换读取器或写入器包是一个相对常见的情况。例如,Excel 文件往往会随着时间的推移而增大,许多用户通过将文件格式从 xlsx 切换到 xlsb 来对抗这一点,因为这可以大幅减小文件大小。在这种情况下,你将不得不从 OpenPyXL 切换到 pyxlsb。这会迫使你重写 OpenPyXL 代码以反映 pyxlsb 的语法。
数据类型转换
这与前面的观点有关:在切换包时,不仅需要调整代码的语法,还需要注意这些包针对相同单元格内容返回的不同数据类型。例如,OpenPyXL 对空单元格返回
None
,而 xlrd 返回空字符串。
单元格循环
读取器和写入器包是低级别包:这意味着它们缺乏能让你轻松处理常见任务的便利函数。例如,大多数包要求你循环遍历每个要读取或写入的单元格。
你可以在配套的仓库中找到 excel.py
模块,并且我们将在即将展示的章节中使用它。作为预览,这里是读取和写入值的语法:
import``excel``values``=``excel``.``read``(``sheet_object``,``first_cell``=``"A1"``,``last_cell``=``None``)``excel``.``write``(``sheet_object``,``values``,``first_cell``=``"A1"``)
read
函数接受来自以下包之一的 sheet
对象:xlrd、OpenPyXL 或 pyxlsb。它还接受可选参数 first_cell
和 last_cell
。它们可以以 A1
表示法或带有 Excel 基于一的索引的行列元组 (1, 1)
提供。first_cell
的默认值是 A1
,而 last_cell
的默认值是已使用范围的右下角。因此,如果只提供 sheet
对象,它将读取整个工作表。write
函数工作方式类似:它期望来自 xlwt、OpenPyXL 或 XlsxWriter 的 sheet
对象以及作为嵌套列表的值,以及一个可选的 first_cell
,它标记了嵌套列表将写入的左上角位置。excel.py
模块还展示了如 表 8-2 所示的数据类型转换的协调工作。
表 8-2. 数据类型转换
Excel 表示 | Python 数据类型 |
---|---|
空单元格 | None |
带有日期格式的单元格 | datetime.datetime (除了 pyxlsb) |
布尔值单元格 | bool |
带有错误的单元格 | str (错误消息) |
字符串 | str |
浮点数 | float 或 int |
现在我们装备好使用 excel.py
模块,准备深入探讨以下几个包:OpenPyXL、XlsxWriter、pyxlsb 和 xlrd/xlwt/xlutils。这些部分都采用了食谱式的样式,让你可以快速入门每个包。建议你根据 表 8-1 选择你需要的包,然后直接跳转到相应的章节,而不是顺序阅读。
WITH 语句
本章中我们会在多个场合使用
with
语句。如果需要温习,请参阅第七章侧边栏 “上下文管理器和 with 语句”。
OpenPyXL
OpenPyXL 是本节唯一一个既能读取又能写入 Excel 文件的包。你甚至可以用它来编辑 Excel 文件——尽管只能是简单的文件。让我们先来看看如何进行读取操作!
使用 OpenPyXL 进行读取
下面的示例代码展示了在使用 OpenPyXL 读取 Excel 文件时执行常见任务的方法。要获取单元格的值,需要使用 data_only=True
打开工作簿。默认为 False
,这会返回单元格的公式而不是值:
In``[``1``]:``import``pandas``as``pd``import``openpyxl``import``excel``import``datetime``as``dt
In``[``2``]:``# 打开工作簿以读取单元格值。``# 加载数据后文件会自动关闭。``book``=``openpyxl``.``load_workbook``(``"xl/stores.xlsx"``,``data_only``=``True``)
In``[``3``]:``# 根据名称或索引(从 0 开始)获取工作表对象``sheet``=``book``[``"2019"``]``sheet``=``book``.``worksheets``[``0``]
In``[``4``]:``# 获取包含所有工作表名称的列表``book``.``sheetnames
Out[4]: ['2019', '2020', '2019-2020']
In``[``5``]:``# 遍历工作表对象。``# openpyxl 使用 "title" 而不是 "name"。``for``i``in``book``.``worksheets``:``print``(``i``.``title``)
2019 2020 2019-2020
In``[``6``]:``# 获取工作表的维度,即工作表的使用范围``sheet``.``max_row``,``sheet``.``max_column
Out[6]: (8, 6)
In``[``7``]:``# 读取单个单元格的值``# 使用 "A1" 表示法和单元格索引(基于 1 的索引)``sheet``[``"B6"``]``.``value``sheet``.``cell``(``row``=``6``,``column``=``2``)``.``value
Out[7]: 'Boston'
In``[``8``]:``# 使用我们的 excel 模块读取单元格值的范围``data``=``excel``.``read``(``book``[``"2019"``],``(``2``,``2``),``(``8``,``6``))``data``[:``2``]``# 打印前两行的数据
Out[8]: [['Store', 'Employees', 'Manager', 'Since', 'Flagship'], ['New York', 10, 'Sarah', datetime.datetime(2018, 7, 20, 0, 0), False]]
使用 OpenPyXL 进行写入
OpenPyXL 在内存中构建 Excel 文件,并在调用 save
方法时写出文件。以下代码生成了 图 8-1 中显示的文件:
In``[``9``]:``import``openpyxl``from``openpyxl.drawing.image``import``Image``from``openpyxl.chart``import``BarChart``,``Reference``from``openpyxl.styles``import``Font``,``colors``from``openpyxl.styles.borders``import``Border``,``Side``from``openpyxl.styles.alignment``import``Alignment``from``openpyxl.styles.fills``import``PatternFill``import``excel
`In
[
10]:
# 实例化一个工作簿book
=openpyxl
.Workbook
()# 获取第一个工作表并命名
sheet=
book.
activesheet
.title
="Sheet1"
# 使用 A1 表示法和单元格索引(从 1 开始)写入单个单元格sheet
["A1"
].
value=
"Hello 1"sheet
.cell
(row
=2
,column
=1
,value
="Hello 2"
)# 格式化:填充颜色,对齐,边框和字体
font_format=
Font(
color=
"FF0000",
bold=
True)
thin=
Side(
border_style=
"thin",
color=
"FF0000")
sheet[
"A3"]
.value
="Hello 3"
sheet[
"A3"]
.font
=font_format
sheet[
"A3"]
.border
=Border
(top
=thin
,left
=thin
,right
=thin
,bottom
=thin
)sheet
["A3"
].
alignment=
Alignment(
horizontal=
"center")
sheet[
"A3"]
.fill
=PatternFill
(fgColor
="FFFF00"
,fill_type
="solid"
)# 数字格式化(使用 Excel 的格式字符串)
sheet[
"A4"]
.value
=3.3333
sheet[
"A4"]
.number_format
="0.00"
# 日期格式化(使用 Excel 的格式字符串)sheet
["A5"
].
value=
dt.
date(
2016,
10,
13)
sheet[
"A5"]
.number_format
="mm/dd/yy"
# 公式:必须使用公式的英文名称# 并使用逗号作为分隔符
sheet[
"A6"]
.value
="=SUM(A4, 2)"
# 图像sheet
.add_image
(Image
("images/python.png"
),"C1"
)# 二维列表(我们正在使用我们的 excel 模块)
data=
[[None
,"North"
,"South"
],[
"Last Year",
2,
5],
["This Year"
,3
,6
]]excel
.write
(sheet
,data
,"A10"
)# 图表
chart=
BarChart()
chart.
type=
"col"chart
.title
="按地区销售"
chart.
x_axis.
title=
"地区"chart
.y_axis
.title
="销售额"
chart_data=
Reference(
sheet,
min_row=
11,
min_col=
1,
max_row=
12,
max_col=
3)
chart_categories=
Reference(
sheet,
min_row=
10,
min_col=
2,
max_row=
10,
max_col=
3)
# from_rows 解释了数据,与在 Excel 中手动添加图表的方式相同chart
.add_data
(chart_data
,titles_from_data
=True
,from_rows
=True
)chart
.set_categories
(chart_categories
)sheet
.add_chart
(chart
,"A15"
)# 保存工作簿将文件保存到磁盘
book.
save(
"openpyxl.xlsx"``)
如果你想要编写一个 Excel 模板文件,在保存之前,你需要将template
属性设置为True
:
In``[``11``]:``book``=``openpyxl``.``Workbook``()``sheet``=``book``.``active``sheet``[``"A1"``]``.``value``=``"这是一个模板"``book``.``template``=``True``book``.``save``(``"template.xltx"``)
正如您在代码中所见,OpenPyXL 通过提供类似FF0000
的字符串来设置颜色。该值由三个十六进制值(FF
、00
和00
)组成,分别对应所需颜色的红/绿/蓝值。十六进制表示十六进制数,使用十六进制基数而不是我们标准十进制系统使用的十进制基数。
查找颜色的十六进制值
要在 Excel 中找到所需颜色的十六进制值,请单击用于更改单元格填充颜色的油漆下拉菜单,然后选择更多颜色。现在选择您的颜色,并从菜单中读取其十六进制值。
图 8-1. 由 OpenPyXL 编写的文件(openpyxl.xlsx)
使用 OpenPyXL 进行编辑
没有读取/写入包可以真正编辑 Excel 文件:实际上,OpenPyXL 读取文件时理解其中的一切,然后再次从头开始编写文件——包括您在其中进行的任何更改。这对于主要包含格式化单元格及数据和公式的简单 Excel 文件非常有用,但对于包含图表和其他更高级内容的电子表格而言,其功能有限,因为 OpenPyXL 要么会更改它们,要么完全删除它们。例如,截至 v3.0.5,OpenPyXL 会重命名图表并删除它们的标题。这里是一个简单的编辑示例:
In``[``12``]:``# 读取 stores.xlsx 文件,更改单元格# 并将其存储在新位置/名称下。``book``=``openpyxl``.``load_workbook``(``"xl/stores.xlsx"``)``book``[``"2019"``][``"A1"``]``.``value``=``"modified"``book``.``save``(``"stores_edited.xlsx"``)
如果您想要编写一个 xlsm 文件,OpenPyXL 必须基于一个现有文件进行操作,您需要使用keep_vba
参数设置为True
加载该文件:
In``[``13``]:``book``=``openpyxl``.``load_workbook``(``"xl/macro.xlsm"``,``keep_vba``=``True``)``book``[``"Sheet1"``][``"A1"``]``.``value``=``"Click the button!"``book``.``save``(``"macro_openpyxl.xlsm"``)
示例文件中的按钮调用了一个显示消息框的宏。 OpenPyXL 涵盖的功能远比我在本节中能够覆盖的要多;因此,建议查看官方文档。在本章末尾,当我们再次接手上一章的案例研究时,我们还将看到更多功能。
XlsxWriter
正如其名称所示,XlsxWriter 只能编写 Excel 文件。以下代码生成了与我们先前用 OpenPyXL 生成的相同工作簿,如图 8-1 所示。请注意,XlsxWriter 使用从零开始的单元格索引,而 OpenPyXL 使用从一开始的单元格索引——如果您在这两种包之间切换,请务必注意这一点:
In``[``14``]:``import``datetime``as``dt``import``xlsxwriter``import``excel
In``[``15``]:``# 实例化一个工作簿``book``=``xlsxwriter``.``Workbook``(``"xlxswriter.xlsx"``)``# 添加一个工作表并命名``sheet``=``book``.``add_worksheet``(``"Sheet1"``)``# 使用 A1 表示法和单元格索引(从 0 开始)写入单个单元格``sheet``.``write``(``"A1"``,``"Hello 1"``)``sheet``.``write``(``1``,``0``,``"Hello 2"``)``# 格式化:填充颜色、对齐、边框和字体``formatting``=``book``.``add_format``({``"font_color"``:``"#FF0000"``,``"bg_color"``:``"#FFFF00"``,``"bold"``:``True``,``"align"``:``"center"``,``"border"``:``1``,``"border_color"``:``"#FF0000"``})``sheet``.``write``(``"A3"``,``"Hello 3"``,``formatting``)``# 数字格式化(使用 Excel 的格式化字符串)``number_format``=``book``.``add_format``({``"num_format"``:``"0.00"``})``sheet``.``write``(``"A4"``,``3.3333``,``number_format``)``# 日期格式化(使用 Excel 的格式化字符串)``date_format``=``book``.``add_format``({``"num_format"``:``"mm/dd/yy"``})``sheet``.``write``(``"A5"``,``dt``.``date``(``2016``,``10``,``13``),``date_format``)``# 公式:您必须使用公式的英文名称,并以逗号作为分隔符``sheet``.``write``(``"A6"``,``"=SUM(A4, 2)"``)``# 图像``sheet``.``insert_image``(``0``,``2``,``"images/python.png"``)``# 二维列表(我们使用我们的 excel 模块)``data``=``[[``None``,``"North"``,``"South"``],``[``"Last Year"``,``2``,``5``],``[``"This Year"``,``3``,``6``]]``excel``.``write``(``sheet``,``data``,``"A10"``)``# 图表:请参阅伴随存储库中的 "sales_report_xlsxwriter.py" 文件,了解如何使用索引而不是单元格地址``chart``=``book``.``add_chart``({``"type"``:``"column"``})``chart``.``set_title``({``"name"``:``"Sales per Region"``})``chart``.``add_series``({``"name"``:``"=Sheet1!A11"``,``"categories"``:``"=Sheet1!B10:C10"``,``"values"``:``"=Sheet1!B11:C11"``})``chart``.``add_series``({``"name"``:``"=Sheet1!A12"``,``"categories"``:``"=Sheet1!B10:C10"``,``"values"``:``"=Sheet1!B12:C12"``})``chart``.``set_x_axis``({``"name"``:``"Regions"``})``chart``.``set_y_axis``({``"name"``:``"Sales"``})``sheet``.``insert_chart``(``"A15"``,``chart``)``# 关闭工作簿会在磁盘上创建文件``book``.``close``()
与 OpenPyXL 相比,XlsxWriter 在编写 xlsm 文件时需要采取更复杂的方法,因为它是一个纯写入包。首先,您需要在 Anaconda Prompt 中从现有的 Excel 文件中提取宏代码(示例使用的是 macro.xlsm 文件,在伴随存储库的 xl 文件夹中):
Windows
首先进入 xl 目录,然后找到 vba_extract.py 的路径,这是一个附带 XlsxWriter 的脚本:
(base)>
cd C:\Users\``username``\python-for-excel\xl
(base)>
where vba_extract.py
C:\Users\``username``\Anaconda3\Scripts\vba_extract.py
然后在以下命令中使用此路径:
(base)>
python C:\...\Anaconda3\Scripts\vba_extract.py macro.xlsm
macOS
在 macOS 上,该命令可作为可执行脚本使用,并可以像这样运行:
(base)>
cd /Users/``username``/python-for-excel/xl
(base)>
vba_extract.py macro.xlsm
这将在运行命令的目录中保存文件 vbaProject.bin。我还在伴随的存储库的 xl 文件夹中包含了提取的文件。我们将在下面的示例中使用它来编写一个带有宏按钮的工作簿:
In``[``16``]:``book``=``xlsxwriter``.``Workbook``(``"macro_xlxswriter.xlsm"``)``sheet``=``book``.``add_worksheet``(``"Sheet1"``)``sheet``.``write``(``"A1"``,``"点击按钮!"``)``book``.``add_vba_project``(``"xl/vbaProject.bin"``)``sheet``.``insert_button``(``"A3"``,``{``"macro"``:``"Hello"``,``"caption"``:``"Button 1"``,``"width"``:``130``,``"height"``:``35``})``book``.``close``()
pyxlsb
与其他读取库相比,pyxlsb 提供的功能较少,但它是读取二进制 xlsb 格式 Excel 文件的唯一选择。pyxlsb 不是 Anaconda 的一部分,因此如果尚未安装,您需要安装它。目前,它也不支持通过 Conda 安装,因此请使用 pip 安装:
(base)>
pip install pyxlsb
您可以按以下方式读取工作表和单元格值:
In``[``17``]:``import``pyxlsb``import``excel
In``[``18``]:``# 循环遍历工作表。使用 pyxlsb,工作簿和工作表对象可以用作上下文管理器。book.sheets 返回的是工作表名称列表,而不是对象!要获取工作表对象,请使用 get_sheet()。``with``pyxlsb``.``open_workbook``(``"xl/stores.xlsb"``)``as``book``:``for``sheet_name``in``book``.``sheets``:``with``book``.``get_sheet``(``sheet_name``)``as``sheet``:``dim``=``sheet``.``dimension````print``(``f``"工作表 '{sheet_name}' 有 "``f``"{dim.h} 行 {dim.w} 列"``)
工作表 '2019' 有 7 行 5 列 工作表 '2020' 有 7 行 5 列 工作表 '2019-2020' 有 20 行 5 列
In``[``19``]:``# 通过我们的 excel 模块读取一段单元格范围的值。与其使用"2019",您也可以使用它的索引(从 1 开始)。``with``pyxlsb``.``open_workbook``(``"xl/stores.xlsb"``)``as``book``:``with``book``.``get_sheet``(``"2019"``)``as``sheet``:``data``=``excel``.``read``(``sheet``,``"B2"``)``data``[:``2``]``# 打印前两行
Out[19]: [['Store', 'Employees', 'Manager', 'Since', 'Flagship'], ['纽约', 10.0, '莎拉', 43301.0, False]]
pyxlsb 目前没有识别日期单元格的方法,因此您必须手动将日期格式的单元格值转换为datetime
对象,如下所示:
In``[``20``]:``from``pyxlsb``import``convert_date``convert_date``(``data``[``1``][``3``])
Out[20]: datetime.datetime(2018, 7, 20, 0, 0)
请记住,如果您使用的是低于 1.3 版本的 pandas,需要显式指定引擎来读取 xlsb 文件格式:
In``[``21``]:``df``=``pd``.``read_excel``(``"xl/stores.xlsb"``,``engine``=``"pyxlsb"``)
xlrd、xlwt 和 xlutils
xlrd、xlwt 和 xlutils 的组合为旧版 xls 格式提供了与 OpenPyXL 对 xlsx 格式提供的大致相同的功能:xlrd 读取、xlwt 写入、xlutils 编辑 xls 文件。这些包已经不再积极开发,但只要仍然有 xls 文件存在,它们可能还会有用。xlutils 不是 Anaconda 的一部分,所以如果还没有安装它,请安装:
(base)>
conda install xlutils
让我们开始阅读部分!
使用 xlrd 进行读取
下面的示例代码向您展示如何使用 xlrd 从 Excel 工作簿中读取数值:
In``[``22``]:``import``xlrd``import``xlwt``from``xlwt.Utils``import``cell_to_rowcol2``import``xlutils``import``excel
In``[``23``]:``# 打开工作簿以读取单元格数值。在加载数据后文件会自动关闭。``book``=``xlrd``.``open_workbook``(``"xl/stores.xls"``)
In``[``24``]:``# 获取所有工作表的名称列表``book``.``sheet_names``()
Out[24]: ['2019', '2020', '2019-2020']
In``[``25``]:``# 遍历工作表对象``for``sheet``in``book``.``sheets``():``print``(``sheet``.``name``)
2019 2020 2019-2020
In``[``26``]:``# 按名称或索引(从 0 开始)获取工作表对象``sheet``=``book``.``sheet_by_index``(``0``)``sheet``=``book``.``sheet_by_name``(``"2019"``)
In``[``27``]:``# 维度``sheet``.``nrows``,``sheet``.``ncols
Out[27]: (8, 6)
In``[``28``]:``# 使用 "A1" 表示法或使用单元格索引(从 0 开始)读取单元格的值。``# "*" 将 cell_to_rowcol2 返回的元组展开为单独的参数。``sheet``.``cell``(``*``cell_to_rowcol2``(``"B3"``))``.``value``sheet``.``cell``(``2``,``1``)``.``value
Out[28]: 'New York'
In``[``29``]:``# 使用我们的 excel 模块读取单元格数值的范围``data``=``excel``.``read``(``sheet``,``"B2"``)``data``[:``2``]``# 打印前两行
Out[29]: [['Store', 'Employees', 'Manager', 'Since', 'Flagship'], ['New York', 10.0, 'Sarah', datetime.datetime(2018, 7, 20, 0, 0), False]]
使用范围
与 OpenPyXL 和 pyxlsb 不同,xlrd 在使用
sheet.nrows
和sheet.ncols
时返回带有值的单元格的维度,而不是工作表的使用范围。Excel 返回的使用范围通常包含底部和右侧的空白行和列。例如,当您删除行内容(通过按删除键)而不是删除行本身(通过右键单击并选择删除)时,就会发生这种情况。
使用 xlwt 进行写入
下面的代码复现了我们之前用 OpenPyXL 和 XlsxWriter 完成的工作,如 图 8-1 所示。但是,xlwt 无法生成图表,只支持 bmp
格式的图片:
In``[``30``]:``import``xlwt``from``xlwt.Utils``import``cell_to_rowcol2``import``datetime``as``dt``import``excel
In``[``31``]:``# 实例化工作簿``book``=``xlwt``.``Workbook``()``# 添加一个工作表并给它命名``sheet``=``book``.``add_sheet``(``"Sheet1"``)``# 使用 A1 表示法和单元格索引(从 0 开始)写入单个单元格``sheet``.``write``(``*``cell_to_rowcol2``(``"A1"``),``"Hello 1"``)``sheet``.``write``(``r``=``1``,``c``=``0``,``label``=``"Hello 2"``)``# 格式化:填充颜色、对齐、边框和字体``formatting``=``xlwt``.``easyxf``(``"font: bold on, color red;"``"align: horiz center;"``"borders: top_color red, bottom_color red,"``"right_color red, left_color red,"``"left thin, right thin,"``"top thin, bottom thin;"``"pattern: pattern solid, fore_color yellow;"``)``sheet``.``write``(``r``=``2``,``c``=``0``,``label``=``"Hello 3"``,``style``=``formatting``)``# 数字格式化(使用 Excel 的格式字符串)``number_format``=``xlwt``.``easyxf``(``num_format_str``=``"0.00"``)``sheet``.``write``(``3``,``0``,``3.3333``,``number_format``)``# 日期格式化(使用 Excel 的格式字符串)``date_format``=``xlwt``.``easyxf``(``num_format_str``=``"mm/dd/yyyy"``)``sheet``.``write``(``4``,``0``,``dt``.``datetime``(``2012``,``2``,``3``),``date_format``)``# 公式:您必须使用逗号作为分隔符的公式的英文名称``sheet``.``write``(``5``,``0``,``xlwt``.``Formula``(``"SUM(A4, 2)"``))``# 二维列表(我们使用我们的 excel 模块)``data``=``[[``None``,``"North"``,``"South"``],``[``"Last Year"``,``2``,``5``],``[``"This Year"``,``3``,``6``]]``excel``.``write``(``sheet``,``data``,``"A10"``)``# 图片(仅允许添加 bmp 格式)``sheet``.``insert_bitmap``(``"images/python.bmp"``,``0``,``2"``)``# 将文件写入磁盘``book``.``save``(``"xlwt.xls"``)
使用 xlutils 进行编辑
xlutils 充当了 xlrd 和 xlwt 之间的桥梁。这明确说明这不是一个真正的编辑操作:通过 xlrd(通过设置 formatting_info=True
)读取包括格式的电子表格,然后由 xlwt 再次写出,包括在其中进行的更改:
In``[``32``]:``import``xlutils.copy
In``[``33``]:``book``=``xlrd``.``open_workbook``(``"xl/stores.xls"``,``formatting_info``=``True``)``book``=``xlutils``.``copy``.``copy``(``book``)``book``.``get_sheet``(``0``)``.``write``(``0``,``0``,``"changed!"``)``book``.``save``(``"stores_edited.xls"``)
现在,您知道如何以特定格式读取和编写 Excel 工作簿了。下一节将介绍一些高级主题,包括处理大型 Excel 文件以及将 pandas 和读取器和写入器包一起使用。
高级读取器和写入器主题
如果你的文件比我们迄今为止在示例中使用的简单 Excel 文件更大更复杂,仅依赖默认选项可能不再足够。因此,我们从讨论如何处理更大的文件开始这一部分。然后,我们将学习如何将 pandas 与 reader 和 writer 包一起使用:这将使你能够以想要的方式设计 pandas 数据帧的样式。最后,我们将运用本章学到的所有知识,使上一章案例研究中的 Excel 报告看起来更加专业。
处理大型 Excel 文件
处理大文件可能会导致两个问题:读写过程可能会很慢,或者您的计算机可能会因内存不足而崩溃。通常,内存问题更加令人担忧,因为它会导致程序崩溃。文件何时被认为是大文件始终取决于系统上的可用资源以及您对慢的定义。本节展示了各个包提供的优化技术,使你能够处理极限的 Excel 文件。我将首先讨论写入库的选项,然后是读取库的选项。在本节末尾,我将向你展示如何并行读取工作簿的工作表以减少处理时间。
使用 OpenPyXL 进行写入
当使用 OpenPyXL 写入大文件时,请确保已安装 lxml 包,因为这会加快写入过程。它已包含在 Anaconda 中,因此你无需做任何操作。然而,关键的选项是write_only=True
标志,它确保内存消耗保持较低水平。但是,它强制你通过使用append
方法逐行写入,并且不再允许你单元格单独写入:
In``[``34``]:``book``=``openpyxl``.``Workbook``(``write_only``=``True``)``# 使用 write_only=True,book.active 不起作用``sheet``=``book``.``create_sheet``()``# 这将生成一个包含 1000 x 200 个单元格的工作表``for``row``in``range``(``1000``):``sheet``.``append``(``list``(``range``(``200``)))``book``.``save``(``"openpyxl_optimized.xlsx"``)
使用 XlsxWriter 写入
XlsxWriter 有一个类似于 OpenPyXL 的选项叫做constant_memory
。它也强制你按顺序写入行。你可以通过提供一个像这样的options
字典来启用该选项:
In``[``35``]:``book``=``xlsxwriter``.``Workbook``(``"xlsxwriter_optimized.xlsx"``,``options``=``{``"constant_memory"``:``True``})``sheet``=``book``.``add_worksheet``()``# 这将生成一个包含 1000 x 200 个单元格的工作表``for``row``in``range``(``1000``):``sheet``.``write_row``(``row``,``0``,``list``(``range``(``200``)))``book``.``close``()
使用 xlrd 读取
当读取旧版 xls 格式的大文件时,xlrd 允许你按需加载工作表,就像这样:
In``[``36``]:``with``xlrd``.``open_workbook``(``"xl/stores.xls"``,``on_demand``=``True``)``as``book``:``sheet``=``book``.``sheet_by_index``(``0``)``# 只加载第一个工作表
如果你不像我们这样使用工作簿作为上下文管理器,你将需要手动调用 book.release_resources()
来正确地再次关闭工作簿。要以这种模式使用 xlrd 和 pandas,像这样使用:
In``[``37``]:``with``xlrd``.``open_workbook``(``"xl/stores.xls"``,``on_demand``=``True``)``as``book``:``with``pd``.``ExcelFile``(``book``,``engine``=``"xlrd"``)``as``f``:``df``=``pd``.``read_excel``(``f``,``sheet_name``=``0``)
使用 OpenPyXL 读取
使用 OpenPyXL 读取大型 Excel 文件时,为了控制内存使用,你应该以 read_only=True
加载工作簿。由于 OpenPyXL 不支持 with
语句,所以在完成后你需要确保关闭文件。如果你的文件包含对外部工作簿的链接,你可能还想使用 keep_links=False
来加快速度。keep_links
确保保留对外部工作簿的引用,这可能会在你只想读取工作簿的值时不必要地减慢过程:
In``[``38``]:``book``=``openpyxl``.``load_workbook``(``"xl/big.xlsx"``,``data_only``=``True``,``read_only``=``True``,``keep_links``=``False``)``# 在此执行所需的读取操作``book``.``close``()``# 使用 read_only=True 时需要关闭
并行读取工作表
当你使用 pandas 的 read_excel
函数读取大型工作簿的多个工作表时,你会发现这需要很长时间(我们稍后会举一个具体的例子)。原因在于 pandas 会顺序地读取工作表,即一个接一个地。为了加快速度,你可以并行读取工作表。虽然由于文件内部结构的原因,写入工作簿的并行化没有简单的方法,但读取多个工作表并行却很简单。然而,由于并行化是一个高级话题,我没有在 Python 介绍中涉及,并且在这里也不会详细讨论。
在 Python 中,如果你想利用每台现代计算机都具备的多个 CPU 核心,你可以使用标准库中的multiprocessing
包。这将会生成多个 Python 解释器(通常每个 CPU 核心一个),并行处理任务。与逐个处理表格不同,你可以让一个 Python 解释器处理第一个表格,同时第二个 Python 解释器处理第二个表格,以此类推。然而,每个额外的 Python 解释器启动需要一些时间,并且使用额外的内存,所以如果你有小文件,当你并行化读取过程时,它们可能比串行化处理更慢。但对于包含多个大表格的大文件,multiprocessing
可以显著加快处理速度——前提是你的系统具备足够的内存来处理工作负载。如果像第二章展示的那样在 Binder 上运行 Jupyter 笔记本,你可能没有足够的内存,因此并行化版本的运行速度会较慢。在伴随的存储库中,你会找到parallel_pandas.py
,它是使用 OpenPyXL 作为引擎的简单实现,用于并行读取表格。使用起来非常简单,因此你不需要了解multiprocessing
的任何内容。
import``parallel_pandas``parallel_pandas``.``read_excel``(``filename``,``sheet_name``=``None``)
默认情况下,它会读取所有表格,但你可以提供一个要处理的表格名称列表。像 pandas 一样,该函数返回一个字典,形式如{"表格名称": df}
,即键是表格名称,值是 DataFrame。
%%TIME 魔术命令
在接下来的示例中,我将使用
%%time
单元格魔术命令。我在第五章中与 Matplotlib 一起介绍了魔术命令。%%time
是一个非常有用的单元格魔术命令,可以用于简单的性能调优,因为它可以轻松比较包含不同代码片段的两个单元格的执行时间。墙时间是从程序(即单元格)开始到结束的经过时间。如果你在 macOS 或 Linux 上,你不仅会得到墙时间,还会得到类似以下这样的 CPU 时间额外行:
CPU times: user 49.4 s, sys: 108 ms, total: 49.5 s
CPU 时间测量 CPU 上花费的时间,这可能低于墙时间(如果程序必须等待 CPU 可用)或高于墙时间(如果程序在多个 CPU 核心上并行运行)。为了更准确地测量时间,请使用
%%timeit
代替%%time
,它会多次运行单元格并取所有运行的平均值。%%time
和%%timeit
是单元格魔术命令,即它们需要在单元格的第一行,并测量整个单元格的执行时间。如果你想测量单独一行代码的时间,可以使用%time
或%timeit
。
让我们看看并行化版本在读取 companion repo 的 xl 文件夹中的 big.xlsx 文件时快多少:
In``[``39``]:``%%``time``data``=``pd``.``read_excel``(``"xl/big.xlsx"``,``sheet_name``=``None``,``engine``=``"openpyxl"``)
Wall time: 49.5 s
In``[``40``]:``%%``time``import``parallel_pandas``data``=``parallel_pandas``.``read_excel``(``"xl/big.xlsx"``,``sheet_name``=``None``)
Wall time: 12.1 s
要获取代表 Sheet1 的 DataFrame,你可以在两种情况下写 data["Sheet1"]
。通过比较两个样本的墙上时间,你会发现,并行化版本在我的笔记本上,对于这个特定的工作簿和 6 个 CPU 内核,比 pd.read_excel
快了数倍。如果你想要更快的速度,直接并行化 OpenPyXL:在 companion repository(parallel_openpyxl.py)中也有一个实现,还有一个用于以并行方式读取传统 xls 格式的 xlrd 实现(parallel_xlrd.py)。通过直接使用底层包而不是 pandas,你可以跳过转换成 DataFrame 或者只应用你需要的清理步骤,这很可能会帮助你加快速度,如果这是你最关心的问题的话。
使用 MODIN 并行读取表格
如果你只读取一个巨大的表格,那么看看 Modin 值得一试,这是一个可以替代 pandas 的项目。它并行化了单表的读取过程,并提供了令人印象深刻的速度改进。由于 Modin 需要特定版本的 pandas,在安装时可能会降级 Anaconda 自带的版本。如果你想测试它,我建议你为此创建一个单独的 Conda 环境,以确保不会影响到基础环境。有关如何创建 Conda 环境的更详细说明,请参阅 附录 A:
(base)>
conda create --name modin python=3.8 -y
(base)>
conda activate modin
(modin)>
conda install -c conda-forge modin -y
在我的机器上,使用 big.xlsx 文件运行以下代码大约需要五秒钟,而 pandas 则需要大约十二秒钟:
import``modin.pandas``data``=``modin``.``pandas``.``read_excel``(``"xl/big.xlsx"``,``sheet_name``=``0``,``engine``=``"openpyxl"``)
现在你知道如何处理大文件了,让我们继续看看如何将 pandas 和低级包一起使用,以改进写入 DataFrame 到 Excel 文件时的默认格式!
格式化 Excel 中的 DataFrame
要按我们想要的方式格式化 Excel 中的 DataFrame,我们可以编写代码,使用 pandas 与 OpenPyXL 或 XlsxWriter 结合使用。我们首先使用这种组合为导出的 DataFrame 添加一个标题。然后在包装这一部分之前,格式化 DataFrame 的标题和索引。将 pandas 与 OpenPyXL 结合用于读取有时也可能很有用,所以让我们从这里开始:
In``[``41``]:``with``pd``.``ExcelFile``(``"xl/stores.xlsx"``,``engine``=``"openpyxl"``)``as``xlfile``:``# 读取 DataFrame``df``=``pd``.``read_excel``(``xlfile``,``sheet_name``=``"2020"``)``# 获取 OpenPyXL 的工作簿对象``book``=``xlfile``.``book``# 从这里开始是 OpenPyXL 代码``sheet``=``book``[``"2019"``]``value``=``sheet``[``"B3"``]``.``value``# 读取单个值
在写入工作簿时,它的功能类似,允许我们轻松地向我们的 DataFrame 报告添加一个标题:
In``[``42``]:``with``pd``.``ExcelWriter``(``"pandas_and_openpyxl.xlsx"``,``engine``=``"openpyxl"``)``as``writer``:``df``=``pd``.``DataFrame``({``"col1"``:``[``1``,``2``,``3``,``4``],``"col2"``:``[``5``,``6``,``7``,``8``]})``# 写入 DataFrame``df``.``to_excel``(``writer``,``"Sheet1"``,``startrow``=``4``,``startcol``=``2``)``# 获取 OpenPyXL 的工作簿和表对象``book``=``writer``.``book``sheet``=``writer``.``sheets``[``"Sheet1"``]``# 从这里开始是 OpenPyXL 代码``sheet``[``"A1"``]``.``value``=``"这是一个标题"``# 写入单元格值
这些示例使用了 OpenPyXL,但概念上与其他包相同。接下来我们继续了解如何格式化 DataFrame 的索引和标头。
格式化 DataFrame 的索引和标头
要完全控制索引和列标题的格式化,最简单的方法是直接编写它们。以下示例分别演示了如何使用 OpenPyXL 和 XlsxWriter 做到这一点。您可以在图 8-2 中查看输出。让我们从创建一个 DataFrame 开始:
In``[``43``]:``df``=``pd``.``DataFrame``({``"col1"``:``[``1``,``-``2``],``"col2"``:``[``-``3``,``4``]},``index``=``[``"row1"``,``"row2"``])``df``.``index``.``name``=``"ix"``df
Out[43]: col1 col2 ix row1 1 -3 row2 -2 4
要使用 OpenPyXL 格式化索引和标头,请按以下步骤操作:
In``[``44``]:``from``openpyxl.styles``import``PatternFill
使用 Openpyxl 格式化索引/标题的示例
In[45]
:
如果要使用 XlsxWriter 格式化索引和标题,请稍微调整代码:
使用 XlsxWriter 格式化索引/标题的示例
In[46]
:
现在索引和标题都已格式化好了,让我们看看如何对数据部分进行样式设置!
图 8-2. 默认格式的 DataFrame(左)和自定义格式(右)
格式化 DataFrame 数据部分
格式化 DataFrame 数据部分的可能性取决于您使用的包:如果使用 pandas 的to_excel
方法,OpenPyXL 可以对每个单元格应用格式,而 XlsxWriter 只能按行或列的方式应用格式。例如,要设置单元格的数字格式为三位小数并居中显示内容,如图 8-3 中所示,使用 OpenPyXL 可以按以下步骤进行:
In``[``47``]:``from``openpyxl.styles``import``Alignment
In``[``48``]:``with``pd``.``ExcelWriter``(``"data_format_openpyxl.xlsx"``,``engine``=``"openpyxl"``)``as``writer``:``# 写出 DataFrame``df``.``to_excel``(``writer``)``# 获取工作簿和工作表对象``book``=``writer``.``book``sheet``=``writer``.``sheets``[``"Sheet1"``]``# 格式化单个单元格``nrows``,``ncols``=``df``.``shape``for``row``in``range``(``nrows``):``for``col``in``range``(``ncols``):``# +1 考虑到标题/索引``# +1 因为 OpenPyXL 基于 1``base````cell``=``sheet``.``cell``(``row``=``row``+``2``,``column``=``col``+``2``)``cell``.``number_format``=``"0.000"``cell``.``alignment``=``Alignment``(``horizontal``=``"center"``)
对于 XlsxWriter,调整代码如下:
In``[``49``]:``with``pd``.``ExcelWriter``(``"data_format_xlsxwriter.xlsx"``,``engine``=``"xlsxwriter"``)``as``writer``:``# 写出 DataFrame``df``.``to_excel``(``writer``)``# 获取工作簿和工作表对象``book``=``writer``.``book``sheet``=``writer``.``sheets``[``"Sheet1"``]``# 格式化列(无法格式化单个单元格)``number_format``=``book``.``add_format``({``"num_format"``:``"0.000"``,``"align"``:``"center"``})``sheet``.``set_column``(``first_col``=``1``,``last_col``=``2``,``cell_format``=``number_format``)
图 8-3. 数据部分格式化的 DataFrame
作为替代方案,pandas 为 DataFrame 的style
属性提供了实验性支持。实验性意味着语法随时可能更改。由于样式用于在 HTML 格式中格式化 DataFrame,因此它们使用 CSS 语法。CSS 代表层叠样式表,用于定义 HTML 元素的样式。要应用与前面示例相同的格式(三位小数和居中对齐),您需要通过applymap
函数将函数应用于Styler
对象的每个元素。您可以通过df.style
属性获得Styler
对象:
In``[``50``]:``df``.``style``.``applymap``(``lambda``x``:``"number-format: 0.000;"``"text-align: center"``)``\
.``to_excel``(``"styled.xlsx"``)
这段代码的输出结果与图 8-3 中显示的一样。有关 DataFrame 样式方法的更多详细信息,请直接参阅样式文档。
无需依赖样式属性,pandas 提供了对日期和日期时间对象进行格式化的支持,如图 8-4 所示:
In``[``51``]:``df``=``pd``.``DataFrame``({``"Date"``:``[``dt``.``date``(``2020``,``1``,``1``)],``"Datetime"``:``[``dt``.``datetime``(``2020``,``1``,``1``,``10``)]})``with``pd``.``ExcelWriter``(``"date.xlsx"``,``date_format``=``"yyyy-mm-dd"``,``datetime_format``=``"yyyy-mm-dd hh:mm:ss"``)``as``writer``:``df``.``to_excel``(``writer``)
图 8-4。带有格式化日期的数据框
其他读取器和写入器包
除了本章中我们已经查看过的软件包之外,还有一些其他的软件包可能对特定的用例有所帮助:
pyexcel
pyexcel提供了一种在不同的 Excel 包和其他文件格式(包括 CSV 文件和 OpenOffice 文件)之间统一的语法。
PyExcelerate
PyExcelerate的目标是以最快的速度编写 Excel 文件。
pylightxl
pylightxl可以读取 xlsx 和 xlsm 文件,并写入 xlsx 文件。
styleframe
styleframe封装了 pandas 和 OpenPyXL,以产生带有精美格式的数据框的 Excel 文件。
oletools
oletools并不是一个传统的读取器或写入器包,但可以用于分析 Microsoft Office 文档,例如,用于恶意软件分析。它提供了一种方便的方法来从 Excel 工作簿中提取 VBA 代码。
现在你知道如何在 Excel 中格式化数据框了,是时候重新审视上一章的案例研究,看看我们是否能够运用本章的知识来改进 Excel 报告了!
案例研究(再访):Excel 报告
翻译到了本章的最后,您已经了解足够的知识,可以回到上一章的 Excel 报告,并使其在视觉上更具吸引力。如果您愿意,可以回到附带存储库中的 sales_report_pandas.py,并尝试将其转换为 Figue 8-5 中显示的报告。
红色数字是低于 20,000 的销售数字。本章未涉及所有格式设置的每个方面(比如如何应用条件格式设置),因此您需要使用您选择的软件包的文档。为了比较您的解决方案,我在附带的存储库中包含了生成此报告的两个脚本版本。第一个版本基于 OpenPyXL(sales_report_openpyxl.py),另一个版本基于 XlsxWriter(sales_report_xlsxwriter.py)。并排查看脚本可能还能让您更明智地决定下一次要选择哪个软件包来完成您的写入任务。我们将在下一章再次回到这个案例研究:在那里,我们将依赖于 Microsoft Excel 的安装来处理报告模板。
图 8-5。由 sales_report_openpyxl.py 创建的重新审视的销售报告
结论
在本章中,我向您介绍了 pandas 在底层使用的读取器和写入器包。直接使用它们允许我们读取和写入 Excel 工作簿,而无需安装 pandas。然而,与 pandas 结合使用使我们能够通过添加标题、图表和格式来增强 Excel DataFrame 报告。虽然当前的读取器和写入器包非常强大,但我仍然希望有一天能看到一个像 "NumPy 时刻" 那样将所有开发者的努力统一到一个项目中。能够在不必首先查看表格的情况下知道使用哪个包,并且在不必为每种类型的 Excel 文件使用不同的语法的情况下使用它,这将是很棒的。从这个意义上说,从 pandas 开始,只有在需要 pandas 未涵盖的附加功能时才退而使用读取器和写入器包,这是有道理的。
然而,Excel 不仅仅是一个数据文件或报告:Excel 应用程序是最直观的用户界面之一,用户可以输入几个数字,并让它显示他们正在寻找的信息。自动化 Excel 应用程序而不是读写 Excel 文件,开启了我们将在第四部分探索的全新功能范围。下一章将通过向您展示如何从 Python 远程控制 Excel 来开始这段旅程。
第四部分:使用 xlwings 编程 Excel 应用程序
第九章:Excel 自动化
到目前为止,我们已经学会了如何用 pandas 替换典型的 Excel 任务(第 II 部分),以及如何将 Excel 文件作为数据源和报告文件格式(第 III 部分)。本章开启了第 IV 部分,在这一部分中,我们不再使用读者和写者包来操作 Excel 文件,而是开始使用 xlwings 自动化 Excel 应用程序。
xlwings 的主要用途是构建交互式应用程序,其中 Excel 电子表格充当用户界面,允许您通过单击按钮或调用用户定义函数来调用 Python —— 这种功能不被读取器和写入器包覆盖。但这并不意味着 xlwings 不能用于读写文件,只要您在 macOS 或 Windows 上安装了 Excel。xlwings 在这方面的一个优势是能够真正编辑 Excel 文件,而不改变或丢失任何现有内容或格式。另一个优势是,您可以从 Excel 工作簿中读取单元格值,而无需先保存它。然而,将 Excel 读取器/写入器包和 xlwings 结合使用也是完全合理的,正如我们将在第七章的报告案例研究中看到的那样。
本章将首先介绍 Excel 对象模型以及 xlwings:我们将首先学习如何连接工作簿、读写单元格数值等基础知识,然后深入了解转换器和选项是如何允许我们处理 pandas 数据帧和 NumPy 数组的。我们还将看看如何与图表、图片和定义名称进行交互,然后转向最后一节,解释 xlwings 在幕后的工作原理:这将为您提供所需的知识,使您的脚本性能更高,并解决缺少功能的问题。从本章开始,您需要在 Windows 或 macOS 上运行代码示例,因为它们依赖于本地安装的 Microsoft Excel。1
开始使用 xlwings
xlwings 的一个目标是作为 VBA 的替代品,允许您在 Windows 和 macOS 上从 Python 与 Excel 进行交互。由于 Excel 的网格是显示 Python 数据结构(如嵌套列表、NumPy 数组和 pandas DataFrame)的理想布局,xlwings 的核心特性之一是尽可能地简化从 Excel 读取和写入这些数据结构。我将从介绍 Excel 作为数据查看器开始这一节——当您在 Jupyter 笔记本中与 DataFrame 交互时,这非常有用。然后我将解释 Excel 对象模型,然后使用 xlwings 进行交互式探索。最后,我将向您展示如何调用可能仍在遗留工作簿中的 VBA 代码。由于 xlwings 是 Anaconda 的一部分,我们不需要手动安装它。
使用 Excel 作为数据查看器
在前几章中,您可能已经注意到,默认情况下,Jupyter 笔记本会隐藏更大的 DataFrame 的大部分数据,仅显示顶部和底部的行以及前几列和最后几列。了解数据的更好方法之一是绘制它——这使您能够发现异常值或其他不规则情况。然而,有时,能够滚动查看数据表确实非常有帮助。在阅读第七章之后,您已经了解如何在 DataFrame 上使用to_excel
方法。虽然这样做可以实现,但可能有些繁琐:您需要为 Excel 文件命名,找到它在文件系统中的位置,打开它,在对 DataFrame 进行更改后,您需要关闭 Excel 文件,并重新运行整个过程。更好的方法可能是运行df.to_clipboard()
,它将 DataFrame df
复制到剪贴板,使您可以将其粘贴到 Excel 中,但更简单的方法是使用 xlwings 提供的view
函数:
In``[``1``]:``# 首先,让我们导入本章将要使用的包``import``datetime``as``dt``import``xlwings``as``xw``import``pandas``as``pd``import``numpy``as``np
`In
[
2]:
# 让我们创建一个基于伪随机数的 DataFrame,并且有足够的行数,以至于只显示头部和尾部df
=pd
.DataFrame
(data
=np
.random
.randn
(100
,5
),columns
=[
f"试验 {i}"
fori
inrange
(1
,6
)])df
Out[2]: Trial 1 Trial 2 Trial 3 Trial 4 Trial 5 0 -1.313877 1.164258 -1.306419 -0.529533 -0.524978 1 -0.854415 0.022859 -0.246443 -0.229146 -0.005493 2 -0.327510 -0.492201 -1.353566 -1.229236 0.024385 3 -0.728083 -0.080525 0.628288 -0.382586 -0.590157 4 -1.227684 0.498541 -0.266466 0.297261 -1.297985 .. ... ... ... ... ... 95 -0.903446 1.103650 0.033915 0.336871 0.345999 96 -1.354898 -1.290954 -0.738396 -1.102659 0.115076 97 -0.070092 -0.416991 -0.203445 -0.686915 -1.163205 98 -1.201963 0.471854 -0.458501 -0.357171 1.954585 99 1.863610 0.214047 -1.426806 0.751906 -2.338352 [100 rows x 5 columns]
In``[``3``]:``# 在 Excel 中查看 DataFrame``xw``.``view``(``df``)
view
函数接受所有常见的 Python 对象,包括数字、字符串、列表、字典、元组、NumPy 数组和 pandas 数据框。默认情况下,它会打开一个新工作簿,并将对象粘贴到第一个工作表的 A1 单元格中——甚至可以使用 Excel 的自动调整功能调整列宽。您还可以通过将 xlwings 的 sheet
对象作为第二个参数提供给 view
函数,以重复使用同一个工作簿:xw.view(df, mysheet)
。如何获取这样的 sheet
对象以及它如何适配到 Excel 对象模型中,这就是我接下来将要解释的内容。2
MACOS:权限和偏好
在 macOS 上,请确保从 Anaconda Prompt(即通过终端)运行 Jupyter 笔记本和 VS Code,如 第二章 所示。这样可以确保第一次使用 xlwings 时会弹出两个弹窗:第一个是“终端想要控制系统事件”,第二个是“终端想要控制 Microsoft Excel”。您需要确认这两个弹窗以允许 Python 自动化 Excel。理论上,任何从中运行 xlwings 代码的应用程序都应该触发这些弹窗,但实际上,通常并非如此,因此通过终端运行它们可以避免麻烦。此外,您需要打开 Excel 的偏好设置,并取消“打开 Excel 时显示工作簿库”选项,该选项在“常规”类别下。这样可以直接在空工作簿中打开 Excel,而不是首先打开库,这样当您通过 xlwings 打开新的 Excel 实例时就不会受到干扰。
Excel 对象模型
当你以编程方式使用 Excel 时,你会与其组件进行交互,比如工作簿或工作表。这些组件在 Excel 对象模型中组织,这是一个层次结构,代表了 Excel 的图形用户界面(见图 9-1)。Microsoft 在所有官方支持的编程语言中基本上使用相同的对象模型,无论是 VBA、Office 脚本(Excel 在 Web 上的 JavaScript 接口)还是 C#。与第八章中的读写包相比,xlwings 非常紧密地遵循了 Excel 对象模型,只是稍微有所创新:例如,xlwings 使用app
代替application
,book
代替workbook
:
-
一个
app
包含books
集合 -
-
一个
book
包含sheets
集合 -
-
一个
sheet
提供对range
对象和集合(如charts
)的访问 -
-
一个
range
包含一个或多个连续的单元格作为其项目
虚线框是集合,包含同一类型的一个或多个对象。一个app
对应于一个 Excel 实例,即运行为单独进程的 Excel 应用程序。高级用户有时会并行使用多个 Excel 实例打开同一工作簿,例如,为了并行计算带有不同输入的工作簿。在更近期的 Excel 版本中,Microsoft 稍微增加了手动打开多个 Excel 实例的复杂性:启动 Excel,然后在 Windows 任务栏中右键单击其图标。在出现的菜单中,同时按住 Alt 键单击 Excel 条目(确保在释放鼠标按钮之后继续按住 Alt 键)——一个弹出窗口会询问是否要启动新的 Excel 实例。在 macOS 上,没有手动启动多个相同程序实例的方式,但是可以通过 xlwings 在编程方式下启动多个 Excel 实例,稍后我们将看到。总之,Excel 实例是一个隔离环境,这意味着一个实例无法与另一个实例通信。3 sheet
对象让您访问诸如图表、图片和定义名称等集合——这些是我们将在本章第二部分中探讨的主题。
图 9-1. 由 xlwings 实现的 Excel 对象模型(节选)
语言和区域设置
本书基于 Excel 的美国英语版本。我偶尔会提到默认名称如“Book1”或“Sheet1”,如果你使用其他语言的 Excel,则名称会不同。例如,法语中的“Sheet1”称为“Feuille1”,西班牙语中称为“Hoja1”。此外,列表分隔符,即 Excel 在单元格公式中使用的分隔符,取决于您的设置:我将使用逗号,但您的版本可能需要分号或其他字符。例如,不是写
=SUM(A1, A2)
,而是在具有德国区域设置的计算机上写=SUMME(A1; A2)
。在 Windows 上,如果您想将列表分隔符从分号更改为逗号,需要在 Excel 之外的 Windows 设置中更改它:点击 Windows 开始按钮,搜索“设置”(或点击齿轮图标),然后转到“时间与语言” > “地区与语言” > “附加日期、时间和区域设置”,最后点击“区域” > “更改位置”。在“列表分隔符”下,您将能够将其从分号更改为逗号。请注意,仅当您的“小数符号”(在同一菜单中)不是逗号时,此设置才有效。要覆盖系统范围内的小数和千位分隔符(但不更改列表分隔符),请在 Excel 中转到“选项” > “高级”,在“编辑选项”下找到相关设置。
在 macOS 上,操作类似,不过你无法直接更改列表分隔符:在 macOS 的系统偏好设置(而非 Excel)中,选择“语言与地区”。在那里,为 Excel(在“应用程序”选项卡下)或全局(在“常规”选项卡下)设置特定的地区。
要熟悉 Excel 对象模型,通常最好是通过互动方式来操作。让我们从 Book
类开始:它允许您创建新工作簿并连接到现有工作簿;参见表格 9-1 以获取概述。
表格 9-1. 使用 Excel 工作簿
命令 | 描述 |
---|---|
xw.Book() |
返回一个表示活动 Excel 实例中新 Excel 工作簿的book 对象。如果没有活动实例,Excel 将会启动。 |
xw.Book("Book1") |
返回一个表示未保存的名为 Book1 的工作簿的book 对象(不带文件扩展名)。 |
xw.Book("Book1.xlsx") |
返回一个表示已保存的名为 Book1.xlsx 的工作簿的book 对象(带有文件扩展名)。文件必须是打开的或者在当前工作目录中。 |
xw.Book(r"C:\path\Book1.xlsx") |
返回一个表示已保存的完整文件路径的工作簿的book 对象。文件可以是打开的或关闭的。使用前缀 r 将字符串转换为原始字符串,使得 Windows 下的反斜杠(\ )被直接解释(我在第五章介绍了原始字符串)。在 macOS 上,不需要 r 前缀,因为文件路径使用正斜杠而不是反斜杠。 |
xw.books.active |
返回活动 Excel 实例中活动工作簿的book 对象。 |
让我们看看如何从 book
对象逐步遍历对象模型层次结构到 range
对象:
In``[``4``]:``# 创建一个新的空工作簿并打印其名称。这是我们将在本章中大多数代码示例中使用的
book。``book``=``xw``.``Book``()``book``.``name
Out[4]: 'Book2'
In``[``5``]:``# 访问工作表集合``book``.``sheets
Out[5]: Sheets([<Sheet [Book2]Sheet1>])
In``[``6``]:``# 通过索引或名称获取工作表对象。如果您的工作表名称不同,您需要调整
Sheet1。``sheet1``=``book``.``sheets``[``0``]``sheet1``=``book``.``sheets``[``"Sheet1"``]
In``[``7``]:``sheet1``.``range``(``"A1"``)
Out[7]: <Range [Book2]Sheet1!$A$1>
通过 range
对象,我们已经到达了层次结构的底部。尖括号中打印的字符串为您提供有关该对象的有用信息,但通常要使用具有属性的对象,如下一个示例所示:
In``[``8``]:``# 最常见的任务:写入值...``sheet1``.``range``(``"A1"``)``.``value``=``[[``1``,``2``],``[``3``,``4``]]``sheet1``.``range``(``"A4"``)``.``value``=``"Hello!"
In``[``9``]:``# ...和读取值``sheet1``.``range``(``"A1:B2"``)``.``value
Out[9]: [[1.0, 2.0], [3.0, 4.0]]
In``[``10``]:``sheet1``.``range``(``"A4"``)``.``value
Out[10]: 'Hello!'
正如您所见,xlwings 的 range
对象的 value
属性默认接受和返回两维范围的嵌套列表和单个单元格的标量。到目前为止,我们几乎与 VBA 完全一致:假设 book
分别是 VBA 或 xlwings 工作簿对象,这是如何从 A1 到 B2 的单元格访问 value
属性的方法:
book``.``Sheets``(``1``)``.``Range``(``"A1:B2"``)``.``Value``# VBA``book``.``sheets``[``0``]``.``range``(``"A1:B2"``)``.``value``# xlwings
差异在于:
属性
Python 使用小写字母,可能带有下划线,如 PEP 8 所建议的 Python 样式指南,我在 第 3 章 中介绍过。
索引
Python 使用方括号和从零开始的索引来访问
sheets
集合中的元素。
表 9-2 提供了 xlwings range
接受的字符串的概述。
表 9-2. 使用 A1 表示法定义范围的字符串
引用 | 描述 |
---|---|
"A1" |
单个单元格 |
"A1:B2" |
从 A1 到 B2 的单元格 |
"A:A" |
A 列 |
"A:B" |
A 到 B 列 |
"1:1" |
第 1 行 |
"1:2" |
1 到 2 行 |
索引和切片适用于 xlwings 的 range
对象 — 注意尖括号中的地址(打印的对象表示)以查看您最终使用的单元格范围:
In``[``11``]:``# 索引``sheet1``.``range``(``"A1:B2"``)[``0``,``0``]
Out[11]: <Range [Book2]Sheet1!$A$1>
In``[``12``]:``# 切片``sheet1``.``range``(``"A1:B2"``)[:,``1``]
Out[12]: <Range [Book2]Sheet1!$B$1:$B$2>
索引对应于在 VBA 中使用 Cells
属性:
book``.``Sheets``(``1``)``.``Range``(``"A1:B2"``)``.``Cells``(``1``,``1``)``# VBA``book``.``sheets``[``0``]``.``range``(``"A1:B2"``)[``0``,``0``]``# xlwings
相反地,您也可以通过索引和切片sheet
对象来获取range
对象,而不是显式地使用range
作为sheet
对象的属性。使用 A1 表示法可以减少输入,使用整数索引可以使 Excel 工作表感觉像 NumPy 数组:
In``[``13``]:``# 单个单元格:A1 表示法``sheet1``[``"A1"``]
Out[13]: <Range [Book2]Sheet1!$A$1>
In``[``14``]:``# 多个单元格:A1 表示法``sheet1``[``"A1:B2"``]
Out[14]: <Range [Book2]Sheet1!$A$1:$B$2>
In``[``15``]:``# 单个单元格:索引``sheet1``[``0``,``0``]
Out[15]: <Range [Book2]Sheet1!$A$1>
In``[``16``]:``# 多个单元格:切片``sheet1``[:``2``,``:``2``]
Out[16]: <Range [Book2]Sheet1!$A$1:$B$2>
有时,通过引用范围的左上角和右下角单元格来定义范围可能更直观。下面的示例分别引用了单元格范围 D10 和 D10:F11,使您可以理解索引/切片sheet
对象与处理range
对象之间的区别:
In``[``17``]:``# 通过工作表索引访问 D10``sheet1``[``9``,``3``]
Out[17]: <Range [Book2]Sheet1!$D$10>
In``[``18``]:``# 通过 range 对象访问 D10``sheet1``.``range``((``10``,``4``))
Out[18]: <Range [Book2]Sheet1!$D$10>
In``[``19``]:``# 通过 sheet 切片访问 D10:F11``sheet1``[``9``:``11``,``3``:``6``]
Out[19]: <Range [Book2]Sheet1!$D$10:$F$11>
In``[``20``]:``# 通过 range 对象访问 D10:F11``sheet1``.``range``((``10``,``4``),``(``11``,``6``))
Out[20]: <Range [Book2]Sheet1!$D$10:$F$11>
使用元组定义range
对象与 VBA 中的Cells
属性非常相似,如下面的比较所示——假设book
再次是 VBA 工作簿对象或 xlwings 的book
对象。让我们首先看看 VBA 版本:
With``book``.``Sheets``(``1``)``myrange``=``.``Range``(.``Cells``(``10``,``4``),``.``Cells``(``11``,``6``))``End``With
这与以下 xlwings 表达式等效:
myrange``=``book``.``sheets``[``0``]``.``range``((``10``,``4``),``(``11``,``6``))
零索引与一索引
作为 Python 包,xlwings 在通过 Python 的索引或切片语法访问元素时始终使用零索引。然而,xlwings 的
range
对象使用 Excel 的一索引行和列索引。与 Excel 用户界面具有相同的行/列索引有时可能是有益的。如果您希望仅使用 Python 的零索引,请简单地使用sheet[row_selection, column_selection]
语法。
下面的示例向您展示如何从range
对象(sheet1["A1"]
)获取到app
对象。请记住,app
对象代表一个 Excel 实例(尖括号中的输出表示 Excel 的进程 ID,因此在您的机器上可能会有所不同):
In``[``21``]:``sheet1``[``"A1"``]``.``sheet``.``book``.``app
Out[21]: <Excel App 9092>
已经到达 Excel 对象模型的顶端,现在是时候看看如何处理多个 Excel 实例了。如果您想在多个 Excel 实例中打开相同的工作簿,或者特别是出于性能原因希望在不同实例中分配您的工作簿,那么您需要明确使用app
对象。使用app
对象的另一个常见用例是在隐藏的 Excel 实例中打开工作簿:这使得您可以在后台运行 xlwings 脚本,同时又不会阻碍您在 Excel 中进行其他工作:
In``[``22``]:``# 从打开的工作簿获取一个应用对象``# 并创建一个额外的不可见应用实例``visible_app``=``sheet1``.``book``.``app``invisible_app``=``xw``.``App``(``visible``=``False``)
In``[``23``]:``# 使用列表推导列出每个实例中打开的书名``[``visible_app``.``books``中的
book.name`]
Out[23]: ['Book1', 'Book2']
In``[``24``]:``[``invisible_app``.``books``中的
book.name`]
Out[24]: ['Book3']
`In
[
25]:
# 应用密钥表示进程 ID(PID)xw
.apps
.keys
()``
Out[25]: [5996, 9092]
`In
[
26]:
# 也可以通过 pid 属性访问xw
.apps
.active
.pid
Out[26]: 5996
`In
[
27]:
# 在不可见的 Excel 实例中处理工作簿invisible_book
=invisible_app
.books
[0
]invisible_book
.sheets
[0
]["A1"
].
value=
"由不可见应用程序创建。"``
In``[``28``]:``# 将 Excel 工作簿保存在 xl 目录中``invisible_book``.``save``(``"xl/invisible.xlsx"``)
In``[``29``]:``# 退出不可见的 Excel 实例``invisible_app``.``quit``()
MACOS:以编程方式访问文件系统
如果您在 macOS 上运行
save
命令,Excel 会弹出授权文件访问的提示窗口,您需要点击“选择”按钮确认,然后再点击“授权访问”。在 macOS 上,Excel 是沙盒化的,这意味着您的程序只能通过确认此提示才能访问 Excel 应用程序外的文件和文件夹。确认后,Excel 将记住位置,在下次运行脚本时再次运行时不会再打扰您。
如果您在两个 Excel 实例中打开相同的工作簿,或者想要指定在哪个 Excel 实例中打开工作簿,就不能再使用xw.Book
了。相反,您需要使用在表 9-3 中描述的books
集合。请注意,myapp
代表一个 xlwings 的app
对象。如果您用xw.books
替换myapp.books
,xlwings 将使用活动的app
。
表 9-3. 使用books
集合操作
命令 | 描述 |
---|---|
myapp.books.add() |
在myapp 所引用的 Excel 实例中创建一个新的 Excel 工作簿,并返回相应的book 对象。 |
myapp.books.open(r"C:\path\Book.xlsx") |
如果该书已打开,则返回book ,否则首先在myapp 引用的 Excel 实例中打开。请记住,前导的r 将文件路径转换为原始字符串,以字面上的方式解释反斜杠。 |
myapp.books["Book1.xlsx"] |
如果该书已打开,则返回book 对象。如果尚未打开,则会引发KeyError 。如果你需要知道工作簿在 Excel 中是否已打开,请使用这个。请确保使用名称而不是完整路径。 |
在我们深入探讨 xlwings 如何替代你的 VBA 宏之前,让我们看看 xlwings 如何与你现有的 VBA 代码交互:如果你有大量的遗留代码,没有时间将所有内容迁移到 Python,这可能非常有用。
运行 VBA 代码
如果你有大量的带有 VBA 代码的遗留 Excel 项目,将所有内容迁移到 Python 可能需要很多工作。在这种情况下,你可以使用 Python 来运行你的 VBA 宏。下面的示例使用了伴随库的 xl 文件夹中的 vba.xlsm 文件。它在 Module1 中包含以下代码:
Function``MySum``(``x``As``Double``,``y``As``Double``)``As``Double``MySum``=``x``+``y``End``Function
Sub``ShowMsgBox``(``msg``As``String``)``MsgBox``msg``End``Sub
要通过 Python 调用这些函数,你首先需要实例化一个 xlwings macro
对象,然后调用它,使其感觉像是本地 Python 函数:
In``[``30``]:``vba_book``=``xw``.``Book``(``"xl/vba.xlsm"``)
In``[``31``]:``# 用 VBA 函数实例化宏对象``mysum``=``vba_book``.``macro``(``"Module1.MySum"``)``# 调用 VBA 函数``mysum``(``5``,``4``)
Out[31]: 9.0
In``[``32``]:``# 使用 VBA Sub 过程同样有效``show_msgbox``=``vba_book``.``macro``(``"Module1.ShowMsgBox"``)``show_msgbox``(``"Hello xlwings!"``)
In``[``33``]:``# 再次关闭该书(确保先关闭 MessageBox)``vba_book``.``close``()
不要将 VBA 函数存储在工作表和此工作簿模块中
如果你将 VBA 函数
MySum
存储在工作簿模块ThisWorkbook
或工作表模块(例如Sheet1
)中,你必须将其称为ThisWorkbook.MySum
或Sheet1.MySum
。然而,你将无法从 Python 访问函数的返回值,所以请确保将 VBA 函数存储在通过在 VBA 编辑器中右键单击模块文件夹插入的标准 VBA 代码模块中。
现在你知道如何与现有的 VBA 代码交互了,我们可以继续探索 xlwings 的使用方法,看看如何与数据框、NumPy 数组和图表、图片以及已定义名称等集合一起使用它。
转换器、选项和集合
在本章的介绍性代码示例中,我们已经通过使用 xlwings 的 range
对象的 value
属性来读取和写入 Excel 中的字符串和嵌套列表。在深入研究允许我们影响 xlwings 读取和写入值的 options
方法之前,我将向您展示如何使用 pandas DataFrames 进行操作。我们继续处理图表、图片和已定义名称,这些通常可以从 sheet
对象访问。掌握这些 xlwings 基础知识后,我们将再次审视第七章 中的报告案例。
处理数据框
将数据框写入 Excel 与将标量或嵌套列表写入 Excel 没有任何区别:只需将数据框分配给 Excel 范围的左上角单元格:
In``[``34``]:``data``=``[[``"Mark"``,``55``,``"Italy"``,``4.5``,``"Europe"``],``[``"John"``,``33``,``"USA"``,``6.7``,``"America"``]]``df``=``pd``.``DataFrame``(``data``=``data``,``columns``=``[``"name"``,``"age"``,``"country"``,``"score"``,``"continent"``],``index``=``[``1001``,``1000``])``df``.``index``.``name``=``"user_id"``df
Out[34]: name age country score continent user_id 1001 Mark 55 Italy 4.5 Europe 1000 John 33 USA 6.7 America
In``[``35``]:``sheet1``[``"A6"``]``.``value``=``df
如果您想抑制列标题和/或索引,则使用以下options
方法:
In``[``36``]:``sheet1``[``"B10"``]``.``options``(``header``=``False``,``index``=``False``)``.``value``=``df
将 Excel 范围作为数据框读取要求您在 options
方法中将 DataFrame
类作为 convert
参数提供。默认情况下,它期望您的数据具有标题和索引,但您可以再次使用 index
和 header
参数进行更改。而不是使用转换器,您还可以首先将值读取为嵌套列表,然后手动构建数据框,但使用转换器可以更轻松地处理索引和标题。
THE EXPAND METHOD
在下面的代码示例中,我将介绍
expand
方法,该方法使得读取一个连续的单元格块变得简单,提供与在 Excel 中执行 Shift+Ctrl+Down-Arrow+Right-Arrow 相同的范围,不同之处在于expand
会跳过左上角的空单元格。
In``[``37``]:``df2``=``sheet1``[``"A6"``]``.``expand``()``.``options``(``pd``.``DataFrame``)``.``value``df2
Out[37]: name age country score continent user_id 1001.0 Mark 55.0 Italy 4.5 Europe 1000.0 John 33.0 USA 6.7 America
In``[``38``]:``# 如果您希望索引是整数索引,则可以更改其数据类型``df2``.``index``=``df2``.``index``.``astype``(``int``)``df2
Out[38]: name age country score continent 1001 Mark 55.0 Italy 4.5 Europe 1000 John 33.0 USA 6.7 America
In``[``39``]:``# 通过设置 index=False,它将把所有从 Excel 中获取的值放入 DataFrame 的数据部分,并使用默认索引``sheet1``[``"A6"``]``.``expand``()``.``options``(``pd``.``DataFrame``,``index``=``False``)``.``value
Out[39]: user_id name age country score continent 0 1001.0 Mark 55.0 Italy 4.5 Europe 1 1000.0 John 33.0 USA 6.7 America
读取和写入 DataFrame 是转换器和选项如何工作的第一个示例。接下来我们将看一下它们是如何正式定义以及如何在其他数据结构中使用的。
转换器和选项
正如我们刚才所看到的,xlwings range
对象的 options
方法允许您影响从 Excel 读取和写入值的方式。也就是说,只有在调用 range
对象的 value
属性时才会评估 options
。语法如下(myrange
是一个 xlwings 的 range
对象):
myrange``.``options``(``convert``=``None``,``option1``=``value1``,``option2``=``value2``,``...``)``.``value
表 9-4 显示了内置转换器,即 convert
参数接受的值。它们被称为内置,因为 xlwings 提供了一种方法来编写自己的转换器,如果需要重复应用额外的转换(例如在写入或读取值之前)时,这将非常有用——要了解它的工作原理,请参阅 xlwings 文档。
表 9-4. 内置转换器
转换器 | 描述 |
---|---|
dict |
简单的无嵌套字典,即 {key1: value1, key2: value2, ...} 的形式 |
np.array |
NumPy 数组,需要 import numpy as np |
pd.Series |
pandas Series,需要 import pandas as pd |
pd.DataFrame |
pandas DataFrame,需要 import pandas as pd |
我们已经在 DataFrame 示例中使用了 index
和 header
选项,但还有更多的选项可用,如 表 9-5 所示。
表 9-5. 内置选项
选项 | 描述 |
---|---|
empty |
默认情况下,空单元格被读取为 None 。通过为 empty 提供值来更改这一点。 |
date |
接受应用于日期格式单元格值的函数。 |
number |
接受应用于数字的函数。 |
ndim |
维度数:在读取时,使用 ndim 强制将范围的值按特定维度到达。必须是 None 、1 或 2 。在读取值作为列表或 NumPy 数组时可用。 |
transpose |
转置值,即将列转换为行或反之。 |
index |
用于 pandas 的 DataFrame 和 Series:在读取时,用于定义 Excel 范围是否包含索引。可以是True /False 或整数。整数定义将多少列转换为MultiIndex 。例如,2 将使用最左边的两列作为索引。在写入时,可以通过将index 设置为True 或False 来决定是否写出索引。 |
header |
与index 相同,但应用于列标题。 |
让我们更仔细地看看ndim
:默认情况下,从 Excel 读取单个单元格时,您会得到一个标量(例如,浮点数或字符串);当从列或行读取时,您会得到一个简单的列表;最后,当从二维范围读取时,您会得到一个嵌套的(即二维的)列表。这不仅在自身上是一致的,而且等同于 NumPy 数组中切片的工作方式,正如在第四章中所见。一维情况是特例:有时,列可能只是否则是二维范围的边缘案例。在这种情况下,通过使用ndim=2
强制范围始终以二维列表形式到达是有意义的:
In``[``40``]:``# 水平范围(一维)``sheet1``[``"A1:B1"``]``.``value
Out[40]: [1.0, 2.0]
In``[``41``]:``# 垂直范围(一维)``sheet1``[``"A1:A2"``]``.``value
Out[41]: [1.0, 3.0]
In``[``42``]:``# 水平范围(二维)``sheet1``[``"A1:B1"``]``.``options``(``ndim``=``2``)``.``value
Out[42]: [[1.0, 2.0]]
In``[``43``]:``# 垂直范围(二维)``sheet1``[``"A1:A2"``]``.``options``(``ndim``=``2``)``.``value
Out[43]: [[1.0], [3.0]]
In``[``44``]:``# 使用 NumPy 数组转换器的行为相同:``# 垂直范围导致一维数组``sheet1``[``"A1:A2"``]``.``options``(``np``.``array``)``.``value
Out[44]: array([1., 3.])
In``[``45``]:``# 保留列的方向``sheet1``[``"A1:A2"``]``.``options``(``np``.``array``,``ndim``=``2``)``.``value
Out[45]: array([[1.], [3.]])
In``[``46``]:``# 如果需要垂直写出列表,则
transpose选项非常方便``sheet1``[``"D1"``]``.``options``(``transpose``=``True``)``.``value``=``[``100``,``200``]
使用ndim=1
强制将单个单元格的值读取为列表而不是标量。在 pandas 中,不需要ndim
,因为 DataFrame 始终是二维的,Series 始终是一维的。这里还有一个例子,展示了empty
、date
和number
选项的工作方式:
In``[``47``]:``# 写入一些示例数据``sheet1``[``"A13"``]``.``value``=``[``dt``.``datetime``(``2020``,``1``,``1``),``None``,``1.0``]
In``[``48``]:``# 使用默认选项读取它``sheet1``[``"A13:C13"``]``.``value
Out[48]: [datetime.datetime(2020, 1, 1, 0, 0), None, 1.0]
In``[``49``]:``# 使用非默认选项将其读取回来``sheet1``[``"A13:C13"``]``.``options``(``empty``=``"NA"``,``dates``=``dt``.``date``,``numbers``=``int``)``.``value
Out[49]: [datetime.date(2020, 1, 1), 'NA', 1]
到目前为止,我们已经使用了book
、sheet
和range
对象。现在让我们继续学习如何处理从sheet
对象访问的图表等集合!
图表、图片和定义名称
在本节中,我将向您展示如何处理通过sheet
或book
对象访问的三个集合:图表、图片和定义名称。4 xlwings 仅支持最基本的图表功能,但由于您可以使用模板工作,您可能甚至不会错过太多内容。而且,为了补偿,xlwings 允许您将 Matplotlib 绘图嵌入为图片——您可能还记得来自第五章的信息,Matplotlib 是 pandas 的默认绘图后端。让我们从创建第一个 Excel 图表开始吧!
Excel 图表
要添加新图表,请使用charts
集合的add
方法,然后设置图表类型和源数据:
In``[``50``]:``sheet1``[``"A15"``]``.``value``=``[[``无``,``"北"``,``"南"``],``[``"上年度"``,``2``,``5``],``[``"今年"``,``3``,``6``]]
In``[``51``]:``chart``=``sheet1``.``charts``.``add``(``top``=``sheet1``[``"A19"``]``.``top``,``left``=``sheet1``[``"A19"``]``.``left``)``chart``.``chart_type``=``"column_clustered"``chart``.``set_source_data``(``sheet1``[``"A15"``]``.``expand``())
这将生成左侧显示的图表,位于图 9-2。要查看可用的图表类型,请参阅xlwings 文档。如果你更喜欢使用 pandas 绘图而不是 Excel 图表,或者想使用 Excel 中没有的图表类型,xlwings 已经为你准备好了——让我们看看吧!
图片:Matplotlib 绘图
当您使用 pandas 的默认绘图后端时,您正在创建一个 Matplotlib 绘图。要将这样的绘图移至 Excel,您首先需要获取其figure
对象,然后将其作为参数提供给pictures.add
——这将把绘图转换为图片并发送至 Excel:
In``[``52``]:``# 将图表数据读取为 DataFrame``df``=``sheet1``[``"A15"``]``.``expand``()``.``options``(``pd``.``DataFrame``)``.``value``df
Out[52]: 北 南 上年度 2.0 5.0 今年 3.0 6.0
In``[``53``]:``# 通过使用 notebook 魔术命令启用 Matplotlib,并切换到
"seaborn"`风格%
matplotlibinline
importmatplotlib.pyplot
asplt
plt.
style.
use(
"seaborn"``)`In
[
54]:
# pandas 绘图方法返回一个“axis”对象,您可以从中获取图表。"T" 转置 DataFrame 以使绘图达到所需方向ax
=df
.T
.plot
.bar
()fig
=ax
.get_figure
()``
In``[``55``]:``# 将图表发送到 Excel``plot``=``sheet1``.``pictures``.``add``(``fig``,``name``=``"SalesPlot"``,``top``=``sheet1``[``"H19"``]``.``top``,``left``=``sheet1``[``"H19"``]``.``left``)``# 让我们将图表缩放到 70%``plot``.``width``,``plot``.``height``=``plot``.``width``*``0.7``,``plot``.``height``*``0.7
要使用新图表更新图片,只需使用update
方法和另一个figure
对象——这实际上将替换 Excel 中的图片,但会保留其所有属性,如位置、大小和名称:
In``[``56``]:``ax``=``(``df``+``1``)``.``T``.``plot``.``bar``()``plot``=``plot``.``update``(``ax``.``get_figure``())
图 9-2. Excel 图表(左)和 Matplotlib 图表(右)
图 9-2 显示了 Excel 图表和 Matplotlib 图表在更新调用后的比较。
确保安装了 PILLOW
在处理图片时,请确保安装了Pillow,Python 的图片处理库:这将确保图片以正确的大小和比例到达 Excel 中。Pillow 是 Anaconda 的一部分,因此如果您使用其他发行版,则需要通过运行
conda install pillow
或pip install pillow
来安装它。请注意,pictures.add
还可以接受磁盘上图片的路径,而不是 Matplotlib 图表。
图表和图片是通过sheet
对象访问的集合。下面我们将看看如何访问定义名称集合,可以从sheet
或book
对象中访问。让我们看看这样做有什么区别!
定义名称
在 Excel 中,通过为范围、公式或常量分配名称来创建定义名称。5 将名称分配给范围可能是最常见的情况,称为命名范围。使用命名范围,您可以在公式和代码中使用描述性名称而不是形如A1:B2
的抽象地址来引用 Excel 范围。与 xlwings 一起使用它们可以使您的代码更加灵活和稳固:从命名范围读取和写入值使您能够重新组织工作簿而无需调整 Python 代码:名称会粘附在单元格上,即使您通过插入新行等操作移动它。定义名称可以设置为全局工作簿范围或局部工作表范围。工作表范围的名称优势在于,您可以复制工作表而无需担心重复命名范围的冲突。在 Excel 中,您可以通过转到公式 > 定义名称或选择范围,然后在名称框中写入所需名称来手动添加定义名称——名称框位于公式栏左侧,默认显示单元格地址。以下是如何使用 xlwings 管理定义名称的方法:
In``[``57``]:``# 默认作用域为工作簿范围``sheet1``[``"A1:B2"``]``.``name``=``"matrix1"
In``[``58``]:``# 对于工作表范围,请使用叹号将工作表名称前缀``sheet1``[``"B10:E11"``]``.``name``=``"Sheet1!matrix2"
In``[``59``]:``# 现在你可以通过名字访问范围``sheet1``[``"matrix1"``]
Out[59]: <Range [Book2]Sheet1!$A$1:$B$2>
In``[``60``]:``# 如果您通过"sheet1"对象访问名称集合,``# 它仅包含该工作表范围内的名称``sheet1``.``names
Out[60]: [<Name 'Sheet1!matrix2': =Sheet1!$B$10:$E$11>]
In``[``61``]:``# 如果您通过"book"对象访问名称集合,``# 它包含所有名称,包括书籍和工作表范围``book``.``names
Out[61]: [<Name 'matrix1': =Sheet1!$A$1:$B$2>, <Name 'Sheet1!matrix2': =Sheet1!$B$10:$E$11>]
In``[``62``]:``# 名称具有各种方法和属性。例如,您可以获取相应的范围对象。``book``.``names``[``"matrix1"``]``.``refers_to_range
Out[62]: <Range [Book2]Sheet1!$A$1:$B$2>
In``[``63``]:``# 如果您想要为常量或公式分配名称,请使用"add"方法``book``.``names``.``add``(``"EURUSD"``,``"=1.1151"``)
Out[63]: <Name 'EURUSD': =1.1151>
查看通过公式 > 名称管理器打开的 Excel 中生成的定义名称(见 Figure 9-3)。请注意,macOS 上的 Excel 没有名称管理器,而是转到公式 > 定义名称,在那里你将看到现有的名称。
图 9-3. 在 xlwings 添加了几个定义名称后的 Excel 名称管理器
现在,您知道如何使用 Excel 工作簿的最常用组件。这意味着我们可以再次从 Chapter 7 看看报告案例研究:让我们看看当我们引入 xlwings 时会发生什么变化!
Case Study (Re-Revisited): Excel Reporting
能够通过 xlwings 真正编辑 Excel 文件使我们能够处理模板文件,无论其多么复杂或存储在何种格式中,都将完全保留,例如,您可以轻松编辑 xlsb 文件,这是当前所有之前章节中的写入包都不支持的情况。当您查看配套存储库中的 sales_report_openpxyl.py 时,您将看到在准备 summary
DataFrame 后,我们需要编写将近四十行代码来创建一个图表并使用 OpenPyXL 样式化一个 DataFrame。而使用 xlwings,您只需六行代码即可实现相同效果,如 Example 9-1 所示。能够处理 Excel 模板中的格式将为您节省大量工作。然而,这也是有代价的:xlwings 需要安装 Excel 才能运行——如果您需要在自己的机器上偶尔创建这些报告,这通常是可以接受的,但如果您试图作为 Web 应用程序的一部分在服务器上创建报告,则可能不太理想。
首先,您需要确保您的 Microsoft Office 许可证覆盖了服务器上的安装,其次,Excel 并不适用于无人值守自动化,这意味着您可能会遇到稳定性问题,尤其是在短时间内需要生成大量报告时。话虽如此,我见过不止一个客户成功地做到了这一点,因此,如果由于某种原因不能使用写入包,将 xlwings 运行在服务器上可能是一个值得探索的选择。只需确保通过 app = xw.App()
在新的 Excel 实例中运行每个脚本,以规避典型的稳定性问题。
您可以在附属存储库中的 sales_report_xlwings.py 中找到完整的 xlwings 脚本(前半部分与我们使用的 OpenPyXL 和 XlsxWriter 相同)。它也是一个完美的示例,展示了如何将读取包与 xlwings 结合使用:尽管 pandas(通过 OpenPyXL 和 xlrd)在从磁盘读取多个文件时更快,但 xlwings 更容易填充预格式化的模板。
示例 9-1. sales_report_xlwings.py(仅第二部分)
# 打开模板,粘贴数据,调整列宽``# 并调整图表数据源。然后以不同的名称保存。``template``=``xw``.``Book``(``this_dir``/``"xl"``/``"sales_report_template.xlsx"``)``sheet``=``template``.``sheets``[``"Sheet1"``]``sheet``[``"B3"``]``.``value``=``summary``sheet``[``"B3"``]``.``expand``()``.``columns``.``autofit``()``sheet``.``charts``[``"Chart 1"``]``.``set_source_data``(``sheet``[``"B3"``]``.``expand``()[:``-``1``,``:``-``1``])``template``.``save``(``this_dir``/``"sales_report_xlwings.xlsx"``)
当您在 macOS 上首次运行此脚本(例如通过在 VS Code 中打开并点击“运行文件”按钮),您将再次确认弹出窗口以授予文件系统访问权限,这是本章早些时候已经遇到的内容。
使用格式化的 Excel 模板,你可以非常快速地创建漂亮的 Excel 报告。你还可以使用 autofit
等方法,这是写入包(如 writer packages)所不具备的功能,因为它依赖 Excel 应用程序进行的计算:这使得你可以根据单元格内容适当设置它们的宽度和高度。图 9-4 展示了由 xlwings 生成的销售报告的上部分,其中包括自定义表头以及应用了 autofit
方法的列。
当你开始使用 xlwings 不仅仅是填充模板中的几个单元格时,了解其内部机制会对你有所帮助:接下来的部分将深入探讨 xlwings 在幕后的工作原理。
图 9-4. 基于预格式化模板的销售报告表格
高级 xlwings 主题
本节将向您展示如何使您的 xlwings 代码更高效,并解决缺少功能的问题。不过,要理解这些主题,我们首先需要简要介绍 xlwings 与 Excel 通信的方式。
xlwings 基础知识
xlwings 依赖于其他 Python 包来与操作系统的自动化机制进行通信:
Windows
在 Windows 上,xlwings 依赖于 COM 技术,即组件对象模型。COM 是一种允许两个进程进行通信的标准——在我们的案例中是 Excel 和 Python。xlwings 使用 Python 包 pywin32 处理 COM 调用。
macOS
在 macOS 上,xlwings 依赖于 AppleScript。AppleScript 是苹果的脚本语言,用于自动化可脚本化的应用程序——幸运的是,Excel 就是这样一个可脚本化的应用程序。为了运行 AppleScript 命令,xlwings 使用 Python 包 appscript。
WINDOWS:如何避免僵尸进程
在 Windows 上使用 xlwings 时,有时会注意到 Excel 看起来完全关闭了,但是当您打开任务管理器(右键单击 Windows 任务栏,然后选择任务管理器)时,在进程选项卡的背景进程下会看到 Microsoft Excel。如果您没有看到任何选项卡,请首先点击“更多详情”。或者,转到详细信息选项卡,在那里您将看到 Excel 列为“EXCEL.EXE”。要终止僵尸进程,请右键单击相应行,然后选择“结束任务”以强制关闭 Excel。
因为这些进程是未终止的不死进程,通常被称为僵尸进程。保留它们会消耗资源,并可能导致不良行为:例如,当您打开新的 Excel 实例时,可能会出现文件被阻塞或加载项未能正确加载的情况。Excel 有时无法正常关闭的原因在于只有在没有 COM 引用(例如 xlwings 的
app
对象形式)时,进程才能被终止。通常,在终止 Python 解释器后,您会遇到 Excel 僵尸进程,因为这会阻止它正确清理 COM 引用。在 Anaconda Prompt 中考虑以下示例:
(base)>
python
>>>
import xlwings as xw
>>>
app = xw.App()
一旦新的 Excel 实例正在运行,请通过 Excel 用户界面再次退出它:虽然 Excel 关闭了,但任务管理器中的 Excel 进程将继续运行。如果您通过运行
quit()
或使用 Ctrl+Z 快捷键来正确关闭 Python 会话,Excel 进程最终会被关闭。然而,如果您在关闭 Excel 之前杀死 Anaconda Prompt,您会注意到该进程作为僵尸进程存在。如果在运行 Jupyter 服务器并在其中一个 Jupyter 笔记本单元格中保持了 xlwings 的app
对象时杀死 Anaconda Prompt,情况也是如此。为了最小化出现 Excel 僵尸进程的可能性,这里有几个建议:
-
从 Python 中运行
app.quit()
而不是手动关闭 Excel。这样可以确保引用被正确清理。 -
-
当你使用 xlwings 时,不要关闭交互式 Python 会话,例如,如果你在 Anaconda Prompt 上运行 Python REPL,请通过运行
quit()
或使用 Ctrl+Z 快捷键来正确关闭 Python 解释器。当你使用 Jupyter 笔记本时,通过在网页界面上点击退出来关闭服务器。 -
-
在交互式 Python 会话中,避免直接使用
app
对象是有帮助的,例如,可以使用xw.Book()
代替myapp.books.add()
。即使 Python 进程被终止,这样做也应该能正确地终止 Excel。
现在你对 xlwings 的基础技术有了了解,让我们看看如何加快慢脚本的速度!
提高性能
为了保持 xlwings 脚本的性能,有几种策略:最重要的是尽量减少跨应用程序调用。使用原始值可能是另一种选择,最后,设置正确的app
属性也可能有所帮助。让我们逐个讨论这些选项!
尽量减少跨应用程序调用
至关重要的是要知道,从 Python 到 Excel 的每个跨应用程序调用都是“昂贵的”,即很慢。因此,应该尽可能减少此类调用。最简单的方法是通过读取和写入整个 Excel 范围而不是遍历单个单元格来实现这一点。在以下示例中,我们首先通过遍历每个单元格,然后通过一次调用处理整个范围,读取和写入 150 个单元格:
In``[``64``]:``# 添加一个新工作表并写入 150 个值``# 以便有点东西可以操作``sheet2``=``book``.``sheets``.``add``()``sheet2``[``"A1"``]``.``value``=``np``.``arange``(``150``)``.``reshape``(``30``,``5``)
In``[``65``]:``%%``time``# 这进行了 150 次跨应用程序调用``for``cell``in``sheet2``[``"A1:E30"``]:``cell``.``value``+=``1
Wall time: 909 ms
In``[``66``]:``%%``time``# 这只进行了两次跨应用程序调用``values``=``sheet2``[``"A1:E30"``]``.``options``(``np``.``array``)``.``value``sheet2``[``"A1"``]``.``value``=``values``+``1
Wall time: 97.2 ms
在 macOS 上,这些数字甚至更加极端,第二个选项比我的机器上的第一个选项快大约 50 倍。
原始值
xlwings 主要设计用于方便使用,而不是速度。但是,如果处理大型单元格范围,可能会遇到可以通过跳过 xlwings 数据清理步骤来节省时间的情况:例如,在读写数据时,xlwings 会遍历每个值,以在 Windows 和 macOS 之间对齐数据类型。通过在 options
方法中使用字符串 raw
作为转换器,可以跳过此步骤。尽管这应该使所有操作更快,但除非在 Windows 上写入大数组,否则差异可能不显著。但是,使用原始值意味着你不能再直接使用 DataFrame 进行工作。相反,你需要将你的值提供为嵌套的列表或元组。此外,你还需要提供写入范围的完整地址——仅提供左上角的单元格不再足够:
In``[``67``]:``# 使用原始值时,必须提供完整的目标范围,sheet["A35"] 不再有效``sheet1``[``"A35:B36"``]``.``options``(``"raw"``)``.``value``=``[[``1``,``2``],``[``3``,``4``]]
应用程序属性
根据工作簿的内容,更改 app
对象的属性也可以帮助加快代码运行速度。通常,你需要查看以下属性(myapp
是 xlwings 的 app
对象):
-
myapp.screen_updating = False
-
-
myapp.calculation = "manual"
-
-
myapp.display_alerts = False
在脚本末尾,确保将属性设置回它们的原始状态。如果你在 Windows 上,通过 xw.App(visible=False)
在隐藏的 Excel 实例中运行脚本,可能还会稍微提高性能。
现在你知道如何控制性能了,让我们看看如何扩展 xlwings 的功能。
如何解决缺失功能
xlwings 为最常用的 Excel 命令提供了 Pythonic 接口,并使其在 Windows 和 macOS 上都能正常工作。然而,Excel 对象模型中有许多方法和属性目前尚未被 xlwings 原生支持,但并非没有办法!xlwings 通过在任何 xlwings 对象上使用 api
属性,让你可以访问 Windows 上的 pywin32 对象和 macOS 上的 appscript 对象。这样一来,你就可以访问整个 Excel 对象模型,但也失去了跨平台兼容性。例如,假设你想清除单元格的格式。下面是如何操作:
-
检查 xlwings
range
对象上是否有可用的方法,例如,在 Jupyter notebook 中在range
对象的末尾加上点后使用 Tab 键,通过运行dir(sheet["A1"])
或搜索 xlwings API 参考。在 VS Code 中,可用方法应自动显示在工具提示中。 -
-
如果所需功能不可用,请使用
api
属性获取底层对象:在 Windows 上,sheet["A1"].api
将给出一个 pywin32 对象,在 macOS 上,将得到一个 appscript 对象。 -
-
查看 Excel VBA 参考 中的 Excel 对象模型。要清除范围的格式,您将最终进入 Range.ClearFormats。
-
-
在 Windows 上,在大多数情况下,您可以直接使用 VBA 方法或属性与您的
api
对象。如果是方法,请确保在 Python 中加上括号:sheet["A1"].api.ClearFormats()
。如果您在 macOS 上进行此操作,则更复杂,因为 appscript 使用的语法可能很难猜测。您最好的方法是查看作为 xlwings 源代码 一部分的开发者指南。然而,清除单元格格式很容易:只需按照 Python 的语法规则使用小写字符和下划线处理方法名称:sheet["A1"].api.clear_formats()
。
如果您需要确保 ClearFormats
在两个平台上都能正常工作,可以按以下方式执行(darwin
是 macOS 的核心,并由 sys.platform
用作其名称):
import``sys``if``sys``.``platform``.``startswith``(``"darwin"``):``sheet``[``"A10"``]``.``api``.``clear_formats``()``elif``sys``.``platform``.``startswith``(``"win"``):``sheet``[``"A10"``]``.``api``.``ClearFormats``()
无论如何,值得在 xlwings 的 GitHub 仓库 上提一个问题,以便在将来的版本中包含该功能。
结论
本章向您介绍了 Excel 自动化的概念:通过 xlwings,您可以使用 Python 完成传统上在 VBA 中完成的任务。我们了解了 Excel 对象模型以及 xlwings 如何允许您与其组件如 sheet
和 range
对象交互。掌握了这些知识,我们回到了第七章 的报告案例研究,并使用 xlwings 填写了一个预先格式化的报告模板;这展示了您可以在读取器包和 xlwings 并行使用的情况。我们还了解了 xlwings 底层使用的库,以了解如何改进性能并解决缺少功能的问题。我最喜欢的 xlwings 功能是它在 macOS 和 Windows 上同样出色。这更令人兴奋,因为 macOS 上的 Power Query 还没有 Windows 版本的所有功能:无论缺少什么功能,您都应该能够轻松用 pandas 和 xlwings 的组合替代它。
现在您已经了解了 xlwings 的基础知识,可以准备好进入下一章了:在那里,我们将迈出下一步,并从 Excel 本身调用 xlwings 脚本,使您能够构建由 Python 驱动的 Excel 工具。
1 在 Windows 上,您至少需要 Excel 2007,在 macOS 上,您至少需要 Excel 2016。或者,您可以安装 Excel 的桌面版,这是 Microsoft 365 订阅的一部分。查看您的订阅以获取有关如何执行此操作的详细信息。
2 注意,xlwings 0.22.0 引入了
xw.load
函数,它类似于xw.view
,但工作方向相反:它允许您轻松将 Excel 范围加载到 Jupyter 笔记本中作为 pandas DataFrame,详见 文档。3 有关单独的 Excel 实例以及其重要性的更多信息,请参见 “什么是 Excel 实例,以及为什么这很重要?”。
4 另一个流行的集合是
tables
。要使用它们,至少需要 xlwings 0.21.0;请参阅 文档。5 带有公式的定义名称也用于 lambda 函数,这是一种在不使用 VBA 或 JavaScript 的情况下定义用户自定义函数的新方法,微软在 2020 年 12 月宣布为 Microsoft 365 订阅用户的新功能。
第十章:Python 驱动的 Excel 工具
在上一章中,我们学习了如何编写 Python 脚本来自动化 Microsoft Excel。虽然这非常强大,但用户必须习惯使用 Anaconda Prompt 或像 VS Code 这样的编辑器来运行脚本。如果您的工具是由业务用户使用,情况可能不会如此。对于他们,您希望隐藏 Python 部分,使 Excel 工具再次感觉像一个普通的启用宏的工作簿。如何通过 xlwings 实现这一点是本章的主题。在查看 xlwings 工具部署的挑战之前,我将首先向您展示从 Excel 运行 Python 代码的最短路径——这也将使我们更详细地了解 xlwings 提供的可用设置。与上一章类似,本章要求您在 Windows 或 macOS 上安装 Microsoft Excel。
使用 Excel 作为前端与 xlwings
前端是应用程序中用户看到并与之交互的部分。其他常见的前端名称包括图形用户界面(GUI)或用户界面(UI)。当我问 xlwings 用户为什么要用 Excel 创建他们的工具,而不是构建现代 Web 应用程序时,我通常听到的答案是:“Excel 是我们的用户熟悉的界面。” 依赖电子表格单元格使用户能够快速直观地提供输入,这使他们通常比使用简陋的 Web 界面更高效。在介绍 xlwings Excel 插件和 xlwings 命令行界面(CLI)之前,我将从quickstart
命令创建我们的第一个项目。在结束本节之前,我将向您展示两种从 Excel 调用 Python 代码的方法:通过单击插件中的“运行主”按钮和使用 VBA 中的RunPython
函数。让我们通过安装 xlwings Excel 插件来开始吧!
Excel 插件
由于 xlwings 包含在 Anaconda 发行版中,在上一章中,我们可以直接在 Python 中运行 xlwings 命令。然而,如果您希望从 Excel 调用 Python 脚本,则需要安装 Excel 插件或在独立模式下设置工作簿。虽然我将在 “部署” 中介绍独立模式,但本节向您展示如何使用插件。要安装插件,请在 Anaconda Prompt 上运行以下命令:
(base)>
xlwings addin install
每当更新 xlwings 时,您需要保持 Python 包的版本和 Excel 插件的版本同步。因此,更新 xlwings 时,您应该始终运行两个命令——一个用于 Python 包,另一个用于 Excel 插件。根据您使用的是 Conda 还是 pip 包管理器,这是更新 xlwings 安装的方式:
Conda(与 Anaconda Python 发行版一起使用)
(base)>
conda update xlwings
(base)>
xlwings addin install
pip(与任何其他 Python 发行版一起使用)
(base)>
pip install --upgrade xlwings
(base)>
xlwings addin install
杀毒软件
不幸的是,有时候 xlwings 插件会被杀毒软件标记为恶意插件,特别是如果你使用的是全新版本。如果这种情况发生在你的电脑上,请转到杀毒软件的设置,在那里你应该能够将 xlwings 标记为安全运行。通常,你也可以通过软件的主页报告此类误报。
当你在 Anaconda Prompt 上输入xlwings
时,你正在使用 xlwings CLI。除了使 xlwings 插件的安装变得容易之外,它还提供了一些其他命令:我将在我们需要时介绍它们,但你随时可以在 Anaconda Prompt 上输入xlwings
并按 Enter 键以打印可用选项。现在让我们更仔细地看看xlwings addin install
做了什么:
安装
插件的实际安装是通过将 xlwings.xlam 从 Python 包目录复制到 Excel 的 XLSTART 文件夹中完成的,这是一个特殊的文件夹:Excel 将在每次启动 Excel 时打开该文件夹中的所有文件。当你在 Anaconda Prompt 上运行
xlwings addin status
时,它会打印 XLSTART 目录在你系统上的位置以及插件是否已安装。
配置
当你首次安装插件时,它也会配置使用你运行
install
命令的 Python 解释器或 Conda 环境:就像你在图 10-1 中看到的那样,Conda Path
和Conda Env
的值会被 xlwings CLI 自动填入。1 这些值会存储在一个名为 xlwings.conf 的文件中,该文件位于你的主目录中的 .xlwings 文件夹中。在 Windows 上,这通常是 C:\Users<用户名>.xlwings\xlwings.conf,在 macOS 上是 /Users/<用户名>/.xlwings/xlwings.conf。在 macOS 上,以点开头的文件和文件夹默认是隐藏的。当你在 Finder 中时,按下键盘快捷键 Command-Shift-. 即可切换它们的可见性。
运行安装命令后,你需要重新启动 Excel 才能看到功能区中的 xlwings 选项卡,如图 10-1 所示。
图 10-1. 运行安装命令后的 xlwings 功能区插件
macOS 上的功能区插件
在 macOS 上,由于缺少关于用户定义函数和 Conda 的部分,功能区看起来有些不同:虽然 macOS 不支持用户定义函数,但 Conda 环境不需要特殊处理,即被配置为 Python 组下的 Interpreter。
现在你已经安装了 xlwings 插件,我们需要一个工作簿和一些 Python 代码来测试它。最快的方法是使用quickstart
命令,接下来我将向你展示。
快速开始命令
为了尽可能简化创建第一个 xlwings 工具的过程,xlwings CLI 提供了quickstart
命令。在 Anaconda Prompt 中,使用cd
命令切换到要创建第一个项目的目录(例如,cd Desktop
),然后运行以下命令以创建名为first_project
的项目:
(base)>
xlwings quickstart first_project
项目名称必须是有效的 Python 模块名称:可以包含字符、数字和下划线,但不能包含空格或破折号,并且不能以数字开头。我将在“RunPython Function”中向你展示如何将 Excel 文件的名称更改为不必遵循这些规则的内容。运行quickstart
命令将在当前目录下创建名为first_project
的文件夹。在 Windows 的文件资源管理器或 macOS 的 Finder 中打开该文件夹,你将看到两个文件:first_project.xlsm 和 first_project.py。在 Excel 中打开 Excel 文件,在 VS Code 中打开 Python 文件。通过使用附加组件中的“Run main”按钮,你可以最简单地从 Excel 运行 Python 代码——我们来看看它是如何工作的!
运行主程序
在更详细查看 first_project.py 之前,请继续点击 xlwings 附加组件最左侧的“Run main”按钮,确保 first_project.xlsm 是你的活动文件;它会将“Hello xlwings!”写入第一个工作表的单元格A1
。再次点击该按钮,它将更改为“Bye xlwings!”恭喜你,你刚刚从 Excel 运行了你的第一个 Python 函数!毕竟,这与编写 VBA 宏并没有太大的区别,对吧?现在让我们在 Example 10-1 中看看 first_project.py。
示例 10-1. first_project.py
import``xlwings``as``xw``def``main``():``wb``=``xw``.``Book``.``caller``()
sheet``=``wb``.``sheets``[``0``]``if``sheet``[``"A1"``]``.``value``==``"Hello xlwings!"``:``sheet``[``"A1"``]``.``value``=``"Bye xlwings!"``else``:``sheet``[``"A1"``]``.``value``=``"Hello xlwings!"``@xw.func
def``hello``(``name``):``return``f``"Hello {name}!"
if
__name__
==``"__main__"``:
xw``.``Book``(``"first_project.xlsm"``)``.``set_mock_caller``()``main``()
xw.Book.caller()
是一个 xlwingsbook
对象,它引用的是在点击“Run main”按钮时处于活动状态的 Excel 工作簿。在我们的情况下,它对应于xw.Book("first_project.xlsm")
。使用xw.Book.caller()
允许你重命名和移动 Excel 文件到文件系统中的其他位置而不会破坏引用。它还确保你在多个 Excel 实例中打开时操作的是正确的工作簿。
在本章中,我们将忽略
hello
函数,因为这将是 Chapter 12 的主题。如果在 macOS 上运行quickstart
命令,你将无法看到hello
函数,因为仅在 Windows 上支持用户定义的函数。
在下一章讨论调试时,我将解释最后三行内容。在本章的目的上,忽略甚至删除第一个函数以下的所有内容。
Excel 加载项中的 Run 主按钮是一个便利功能:它允许你调用 Python 模块中与 Excel 文件同名的main
函数,而无需首先向工作簿添加按钮。即使你将工作簿保存为无宏的 xlsx 格式,它也能正常工作。但是,如果你想调用一个或多个不叫main
且不属于与工作簿同名模块的 Python 函数,你必须使用 VBA 中的RunPython
函数。接下来的部分详细介绍了相关内容!
RunPython 函数
如果你需要更多控制如何调用你的 Python 代码,可以使用 VBA 函数RunPython
。因此,RunPython
要求你的工作簿保存为启用宏的工作簿。
启用宏
当你打开一个启用宏的工作簿(xlsm 扩展名)时(例如通过
quickstart
命令生成的工作簿),你需要点击“启用内容”(Windows)或“启用宏”(macOS)。在 Windows 上,当你使用伴随库中的 xlsm 文件时,你还必须点击“启用编辑”,否则 Excel 无法正确打开从互联网下载的文件。
RunPython
接受一个包含 Python 代码的字符串:通常情况下,你会导入一个 Python 模块并运行其中的一个函数。当你通过 Alt+F11(Windows)或 Option-F11(macOS)打开 VBA 编辑器时,你会看到quickstart
命令在名为“Module1”的 VBA 模块中添加了一个名为SampleCall
的宏(参见 Figure 10-2)。如果你看不到SampleCall
,请在左侧的 VBA 项目树中双击 Module1。
图 10-2. VBA 编辑器显示 Module1
代码看起来有些复杂,但这仅是为了使其能动态工作,无论你在运行quickstart
命令时选择了什么项目名称。由于我们的 Python 模块名为first_project
,你可以用以下易于理解的等效代码替换它:
Sub``SampleCall``()``RunPython``"import first_project; first_project.main()"``End``Sub
由于在 VBA 中写多行字符串并不好玩,我们使用了 Python 接受的分号而不是换行符。你可以有几种方式运行这段代码:例如,在 VBA 编辑器中时,将光标放在SampleCall
宏的任一行上,然后按 F5。通常情况下,你会从 Excel 工作表而不是 VBA 编辑器运行代码。因此,关闭 VBA 编辑器并切换回工作簿。在 Windows 上,键入 Alt+F8 或 macOS 上的 Option-F8 将显示宏菜单:选择SampleCall
并点击运行按钮。或者,为了使其更加用户友好,在你的 Excel 工作簿中添加一个按钮并将其与SampleCall
连接起来:首先确保在功能区中显示了开发人员选项卡。如果没有显示,请转到文件
> 选项
> 自定义功能区
并激活开发人员旁边的复选框(在 macOS 上,你可以在 Excel > 首选项
> 功能区和工具栏
下找到它)。要插入按钮,请转到开发人员选项卡,在控件组中点击插入
> 按钮
(在表单控件下)。在 macOS 上,你将直接看到按钮,无需先进入插入选项。当你点击按钮图标时,你的光标会变成一个小十字:使用它通过按住左键并绘制一个矩形形状在你的工作表上绘制一个按钮。一旦释放鼠标按钮,你将看到分配宏菜单——选择SampleCall
并点击确定。点击你刚刚创建的按钮(在我的情况下是“Button 1”),它将再次运行我们的main
函数,就像在图 10-3 中一样。
图 10-3。在工作表上绘制按钮
FORM CONTROLS VS. ACTIVEX CONTROLS
在 Windows 上,你有两种类型的控件:表单控件和 ActiveX 控件。虽然你可以从任一组中使用按钮连接到你的
SampleCall
宏,但只有来自表单控件的按钮在 macOS 上也能正常工作。在下一章中,我们将使用矩形作为按钮,使其看起来更现代化。
现在让我们看看如何更改由quickstart
命令分配的默认名称:返回到你的 Python 文件,并将其从first_project.py
重命名为hello.py
。同时,将你的main
函数改名为hello_world
。确保保存文件,然后再次通过 Alt+F11(Windows)或 Option-F11(macOS)打开 VBA 编辑器,并编辑SampleCall
如下以反映这些更改:
Sub``SampleCall``()``RunPython``"import hello; hello.hello_world()"``End``Sub
回到表格,点击“按钮 1”确保一切正常。最后,您可能还希望将 Python 脚本和 Excel 文件保存在两个不同的目录中。要理解这一点的含义,我首先需要简单介绍一下 Python 的模块搜索路径:如果在您的代码中导入一个模块,Python 会在各个目录中查找它。首先,Python 会检查是否有内置模块与此名称相匹配,如果找不到,则继续查找当前工作目录和所谓的PYTHONPATH
中的目录。xlwings 会自动将工作簿的目录添加到PYTHONPATH
,并允许您通过插件添加额外的路径。要尝试这一功能,请将现在名为 hello.py 的 Python 脚本移动到一个名为 pyscripts 的文件夹中,该文件夹位于您的主目录下:在我的情况下,这将是 Windows 上的 C:\Users\felix\pyscripts 或 macOS 上的 /Users/felix/pyscripts。现在再次点击按钮时,您将在弹出窗口中收到以下错误:
Traceback (most recent call last): File "<string>", line 1, in <module> ModuleNotFoundError: No module named 'first_project'
要解决此问题,只需在 xlwings 标签中的PYTHONPATH
设置中添加 pyscripts 目录的路径,如图 10-4 所示。现在再次单击按钮时,它将再次正常工作。
图 10-4. PYTHONPATH 设置
我还没有提到的是 Excel 工作簿的名称:一旦您的RunPython
函数调用使用显式模块名称如first_project
而不是通过quickstart
添加的代码,您可以随意将 Excel 工作簿重命名为任何您想要的名称。
如果您开始一个新的 xlwings 项目,依赖quickstart
命令是最简单的方式。然而,如果您有一个现有的工作簿,您可能更喜欢手动设置。让我们看看如何操作!
不使用 quickstart 命令的 RunPython
如果您想要使用RunPython
函数处理未通过quickstart
命令创建的现有工作簿,您需要手动处理quickstart
命令为您处理的事情。请注意,以下步骤仅适用于RunPython
调用,而不适用于您想要使用主按钮的 Run:
-
首先确保将工作簿另存为带有 xlsm 或 xlsb 扩展名的宏启用工作簿。
-
-
添加 VBA 模块;要添加 VBA 模块,请通过 Alt+F11(Windows)或 Option-F11(macOS)打开 VBA 编辑器,并确保在左侧树视图中选择工作簿的 VBAProject,然后右键单击它,选择“插入” > “模块”,如图 10-5 所示。这将插入一个空的 VBA 模块,您可以在其中编写带有
RunPython
调用的 VBA 宏。 -
图 10-5. 添加一个 VBA 模块
-
添加对 xlwings 的引用:
RunPython
是 xlwings 插件的一部分。要使用它,您需要确保在您的 VBA 项目中设置了对xlwings
的引用。同样,首先在 VBA 编辑器左侧的树视图中选择正确的工作簿,然后转到工具 > 引用,并激活 xlwings 的复选框,如图 10-6 所示。
现在您的工作簿已准备好再次使用RunPython
调用。一旦一切在您的机器上正常运行,下一步通常是让它在您同事的机器上工作 — 让我们来看看几种使这部分更容易的选项!
图 10-6. RunPython 需要引用 xlwings。
部署
在软件开发中,部署一词指的是分发和安装软件,以便最终用户能够使用它。在 xlwings 工具的情况下,了解所需的依赖项和可以简化部署的设置非常重要。我将从最重要的依赖项 Python 开始讲起,然后看看已经设置为独立模式的工作簿,以摆脱 xlwings Excel 插件。我将通过更详细地查看 xlwings 配置如何工作来结束本节。
Python 依赖
要能够运行 xlwings 工具,您的最终用户必须安装 Python。但仅因为他们还没有安装 Python 并不意味着没有简化安装过程的方法。以下是几种选择:
Anaconda 或 WinPython
指导用户下载并安装 Anaconda 发行版。为了安全起见,您可能需要同意特定版本的 Anaconda,以确保他们使用的是与您相同的包含包的版本。如果您只使用 Anaconda 中包含的软件包,这是一个不错的选择。WinPython 是 Anaconda 的一个有趣的替代品,它在 MIT 开源许可下发布,并且也预装了 xlwings。顾名思义,它只适用于 Windows 系统。
共享驱动器
如果您可以访问一个相对快速的共享驱动器,您可能能够直接在那里安装 Python,这将允许每个人在没有本地 Python 安装的情况下使用工具。
冻结的可执行文件
在 Windows 上,xlwings 允许你使用冻结的可执行文件,这些文件具有 .exe 扩展名,包含 Python 和所有依赖项。生产冻结可执行文件的流行包是 PyInstaller。冻结可执行文件的优点是它们只打包你的程序所需的内容,并可以生成单个文件,这可以使分发更加容易。有关如何使用冻结可执行文件的详细信息,请查看 xlwings 文档。请注意,当你使用 xlwings 用于用户定义函数时,冻结可执行文件将无法使用,这是我将在 第十二章 中介绍的功能。
虽然 Python 是硬性要求,但不需要安装 xlwings 插件,接下来我会解释原因。
独立工作簿:摆脱 xlwings 插件
在本章中,我们一直依赖 xlwings 插件通过单击“运行主按钮”或使用 RunPython
函数来调用 Python 代码。即使 xlwings CLI 使得安装插件变得简单,对于不太熟悉使用 Anaconda Prompt 的非技术用户来说,可能仍然有些麻烦。此外,由于 xlwings 插件和 xlwings Python 包需要相同的版本,你可能会遇到收件人已经安装了与你的工具所需版本不同的 xlwings 插件的冲突。不过,有一个简单的解决方案:xlwings 不需要 Excel 插件,可以设置为独立的工作簿。在这种情况下,插件的 VBA 代码直接存储在你的工作簿中。通常,设置所有内容的最简单方式是使用 quickstart
命令,这次使用 --standalone
标志:
(base)>
xlwings quickstart second_project --standalone
当你在 Excel 中打开生成的 second_project.xlsm 工作簿并按 Alt+F11(Windows)或 Option-F11(macOS)时,你会看到需要放置插件的 xlwings
模块和 Dictionary
类模块。最重要的是,独立项目不再需要引用 xlwings。尽管使用 --standalone
标志时会自动配置这一点,但如果要转换现有工作簿,则重要的是你在 VBA 编辑器的工具 > 引用中取消勾选 xlwings
复选框。
构建自定义插件
虽然本节展示了如何摆脱 xlwings 插件的依赖性,但有时你可能希望为部署构建自己的插件。如果你希望在许多不同的工作簿中使用相同的宏,这是有道理的。你可以在 xlwings 文档 中找到构建自定义插件的说明。
已经涉及 Python 和插件,现在让我们更深入地了解 xlwings 配置是如何工作的。
配置层次结构
正如本章开头提到的,Ribbon 存储其配置在用户主目录下的 .xlwings\xlwings.conf 文件中。配置包括像我们在本章开头已经看到的 PYTHONPATH
这样的个别设置。你在插件中设置的设置可以在目录和工作簿级别上被覆盖 — xlwings 会按照以下位置和顺序查找设置:
工作簿配置
首先,xlwings 将查找一个名为 xlwings.conf 的工作表。这是配置你的工作簿进行部署的推荐方式,因为你不需要处理额外的配置文件。当你运行
quickstart
命令时,它将在名为 "_xlwings.conf" 的工作表上创建一个示例配置:去掉名称前面的下划线以激活它。如果你不想使用它,可以随意删除该工作表。
目录配置
接下来,xlwings 将在 Excel 工作簿所在目录中查找一个名为 xlwings.conf 的文件。
用户配置
最后,xlwings 将在用户主目录的 .xlwings 文件夹中查找一个名为 xlwings.conf 的文件。通常情况下,你不直接编辑这个文件 — 而是通过插件在你修改设置时自动创建和编辑它。
如果 xlwings 在这三个位置找不到任何设置,它将回退到默认值。
当你通过 Excel 插件编辑设置时,它会自动编辑 xlwings.conf 文件。如果你想直接编辑文件,请访问 xlwings 文档 查找确切的格式和可用设置,但接下来我会在部署的上下文中指出最有帮助的设置。
设置
最关键的设置当然是 Python 解释器 — 如果你的 Excel 工具找不到正确的 Python 解释器,什么都不会正常工作。PYTHONPATH
设置允许你控制放置 Python 源文件的位置,而使用 UDF 服务器设置可以在 Windows 上在调用之间保持 Python 解释器运行,这可以极大地提高性能。
Python 解释器
xlwings 依赖于本地安装的 Python 程序。然而,这并不一定意味着你的 xlwings 工具的接收者需要在使用工具之前进行配置。如前所述,你可以建议他们安装 Anaconda 发行版,并选择默认设置,这将会将其安装在用户的主目录下。如果在配置中使用环境变量,xlwings 将会找到正确的 Python 解释器路径。环境变量是设置在用户计算机上的变量,允许程序查询特定于该环境的信息,比如当前用户主目录的名称。例如,在 Windows 上,将
Conda Path
设置为%USERPROFILE%\anaconda3
,而在 macOS 上,将Interpreter_Mac
设置为$HOME/opt/anaconda3/bin/python
。这些路径将动态解析为 Anaconda 的默认安装路径。
PYTHONPATH
默认情况下,xlwings 在与 Excel 文件相同的目录中查找 Python 源文件。当您将工具交给不熟悉 Python 的用户时,这可能不是理想的做法,因为移动 Excel 文件时他们可能会忘记保持这两个文件在一起。相反,您可以将 Python 源文件放在一个专用文件夹中(可以是共享驱动器上的文件夹),并将此文件夹添加到
PYTHONPATH
设置中。或者,您也可以将源文件放在 Python 模块搜索路径中的路径上。实现这一目标的一种方法是将您的源代码作为 Python 包进行分发 — 安装后会将其放置在 Python 的 site-packages 目录中,Python 将在那里找到您的代码。有关如何构建 Python 包的更多信息,请参阅 Python Packaging User Guide。
RunPython: 使用 UDF 服务器(仅限 Windows)
您可能已经注意到,
RunPython
调用可能会相当慢。这是因为 xlwings 启动一个 Python 解释器,运行 Python 代码,然后再次关闭解释器。这在开发过程中可能不算太糟糕,因为它确保每次调用RunPython
命令时都会从头开始加载所有模块。不过,一旦您的代码稳定下来,您可能希望激活“RunPython: Use UDF Server”复选框(仅在 Windows 上可用)。这将使用与用户定义函数使用的相同 Python 服务器(第十二章的主题,参见 Chapter 12)并在调用之间保持 Python 会话运行,速度将会快得多。但请注意,您需要在代码更改后单击功能区中的“重新启动 UDF 服务器”按钮。XLWINGS PRO
尽管本书仅使用免费和开源版本的xlwings,但也提供了商业版 PRO,用于支持持续的开源版本维护和开发。xlwings PRO 提供的一些额外功能包括:
-
Python 代码可以嵌入到 Excel 中,从而摆脱外部源文件。
-
-
报告包允许您将工作簿转换为带有占位符的模板。这使非技术用户能够编辑模板,而无需更改 Python 代码。
-
-
可轻松构建安装程序,摆脱任何部署问题:最终用户可以通过单击安装 Python 及其所有依赖项,给他们带来处理普通 Excel 工作簿的感觉,而无需手动配置任何内容。
-
有关 xlwings PRO 的更多详细信息及申请试用许可证,请参阅 xlwings 主页。
结论
本章首先向您展示了如何轻松地从 Excel 中运行 Python 代码:只需安装 Anaconda,然后运行xlwings addin install
,接着运行xlwings quickstart myproject
,就可以点击 xlwings 加载项中的“运行主程序”按钮或使用RunPython
VBA 函数。第二部分介绍了一些设置,使得部署 xlwings 工具到最终用户变得更加容易。xlwings 预先安装在 Anaconda 中这一事实,在降低新用户的入门难度方面起到了很大的作用。
在本章中,我们仅使用了 Hello World 示例来学习一切是如何工作的。下一章将利用这些基础知识构建 Python 包跟踪器,一个完整的商业应用程序。
1 如果您使用的是 macOS 或者使用的是 Anaconda 之外的 Python 发行版,它会配置解释器而不是 Conda 设置。
第十一章:Python 包追踪器
在本章中,我们将创建一个典型的业务应用程序,该应用程序从互联网上下载数据并将其存储在数据库中,然后在 Excel 中进行可视化。这将帮助您理解 xlwings 在这种应用程序中的作用,并演示使用 Python 连接外部系统的简易性。为了尝试构建一个接近真实应用程序但相对简单易懂的项目,我想出了 Python 包追踪器,这是一个 Excel 工具,显示给定 Python 包每年发布次数。尽管是案例研究,您实际上可能会发现该工具有助于了解特定 Python 包是否正在积极开发中。
熟悉了应用程序之后,我们将讨论几个需要理解的主题,以便能够跟进其代码:我们将看到如何从互联网上下载数据,以及如何与数据库交互,然后我们将学习 Python 中的异常处理,这是应用程序开发中的重要概念。完成这些前提后,我们将查看 Python 包追踪器的组件,了解所有内容如何相互关联。最后,我们将探讨如何调试 xlwings 代码的工作方式。与前两章一样,本章要求您在 Windows 或 macOS 上安装 Microsoft Excel。让我们开始使用 Python 包追踪器进行测试吧!
我们将要构建什么
转到配套代码库,您将找到 packagetracker 文件夹。该文件夹中有一些文件,但现在只需打开 Excel 文件 packagetracker.xlsm 并转到数据库表:我们首先需要将一些数据输入数据库,以便有东西可以操作。如图 Figure 11-1 所示,输入一个包名称,例如“xlwings”,然后点击“添加包”。您可以选择任何存在于 Python 包索引(PyPI)上的包名称。
MACOS:确认对文件夹的访问权限
当您在 macOS 上添加第一个包时,您需要确认一个弹出窗口,以便应用程序可以访问 packagetracker 文件夹。这与我们在 第九章 中遇到的弹出窗口相同。
图 11-1. Python 包追踪器(数据库表)
如果一切按计划进行,您将在输入包名称的右侧看到消息“成功添加了 xlwings”。此外,在“更新数据库”部分下,您还将看到最后更新的时间戳,以及更新的日志部分,显示已成功下载 xlwings 并将其存储到数据库中。让我们再做一次,添加 pandas 包,这样我们就有更多的数据可以玩耍了。现在,切换到“Tracker”工作表,在单元格 B5 的下拉菜单中选择 xlwings,然后点击“显示历史记录”。您的屏幕现在应该类似于图 11-2,显示了包的最新版本以及多年来发布次数的图表。
图 11-2. Python 包追踪器(Tracker 工作表)
您现在可以返回到“数据库”工作表,并添加其他包。每当您想要从 PyPI 更新数据库中的最新信息时,请点击“更新数据库”按钮:这将使您的数据库与来自 PyPI 的最新数据同步。
在从用户角度了解 Python 包追踪器的工作方式之后,现在让我们介绍其核心功能。
核心功能
在本节中,我将向您介绍 Python 包追踪器的核心功能:如何通过 Web API 获取数据以及如何查询数据库。我还将向您展示如何处理异常,这是在编写应用程序代码时不可避免的主题。让我们从 Web API 开始吧!
Web API
Web API 是应用程序从互联网获取数据的最流行方式之一:API 代表应用程序编程接口,并定义了如何以编程方式与应用程序进行交互。因此,Web API 是通过网络访问的 API,通常是互联网。为了了解 Web API 的工作原理,让我们退后一步,看看当您在浏览器中打开网页时(简化说),会发生什么:在地址栏中输入 URL 后,浏览器向服务器发送 GET 请求,请求您想要的网页。GET 请求是 HTTP 协议的一种方法,您的浏览器用它来与服务器通信。当服务器收到请求时,它会通过发送请求的 HTML 文档作出响应,您的浏览器则显示该文档:您的网页已加载完成。HTTP 协议还有各种其他方法;除了 GET 请求之外,最常见的方法是 POST 请求,用于将数据发送到服务器(例如,当您在网页上填写联系表单时)。
对于服务器向人类发送精美格式的 HTML 页面以进行交互是有意义的,但应用程序不关心设计,只关心数据。因此,对 web API 的 GET 请求类似于请求网页,但通常会以 JSON 格式而不是 HTML 格式返回数据。JSON 代表 JavaScript 对象表示法,是一种几乎所有编程语言都能理解的数据结构,这使其成为在不同系统之间交换数据的理想选择。尽管其符号使用 JavaScript 语法,但它与 Python 中使用(嵌套)字典和列表的方式非常接近。它们的区别如下:
-
JSON 只接受双引号作为字符串的引号符号
-
-
JSON 中使用
null
表示 Python 中的None
-
-
JSON 中使用小写的
true
和false
,而 Python 中使用大写 -
-
JSON 只接受字符串作为键,而 Python 字典可以接受广泛的对象作为键
标准库中的json
模块允许你将 Python 字典转换为 JSON 字符串,反之亦然:
In``[``1``]:``import``json
In``[``2``]:``# 一个 Python 字典...``user_dict``=``{``"name"``:``"Jane Doe"``,``"age"``:``23``,``"married"``:``False``,``"children"``:``None``,``"hobbies"``:``[``"hiking"``,``"reading"``]}
In``[``3``]:``# ...通过 json.dumps(“dump string”)转换为 JSON 字符串。
indent参数是可选的,可以美化打印。``user_json``=``json``.``dumps``(``user_dict``,``indent``=``4``)``print``(``user_json``)
{ "name": "Jane Doe", "age": 23, "married": false, "children": null, "hobbies": [ "hiking", "reading" ] }
In``[``4``]:``# 将 JSON 字符串转换回本地 Python 数据结构``json``.``loads``(``user_json``)
Out[4]: {'name': 'Jane Doe', 'age': 23, 'married': False, 'children': None, 'hobbies': ['hiking', 'reading']}
REST API
通常不是 web API,你会经常看到术语 REST 或 RESTful API。REST 代表表述性状态传递,并定义了遵循特定约束的 web API。其核心思想是通过无状态资源访问信息。无状态意味着对 REST API 的每个请求都完全独立于任何其他请求,并且始终需要提供其请求的完整信息集。请注意,术语 REST API 经常被误用来表示任何类型的 web API,即使它不遵循 REST 约束。
使用 web API 通常非常简单(我们稍后将看到如何在 Python 中使用),几乎每个服务都提供这样的接口。如果你想下载你最喜爱的 Spotify 播放列表,你可以发出以下 GET 请求(参见Spotify Web API Reference):
GET https://api.spotify.com/v1/playlists/``playlist_id
或者,如果你想获取关于你最新 Uber 行程的信息,你可以执行以下 GET 请求(参见Uber REST API):
GET https://api.uber.com/v1.2/history
虽然要使用这些 API,您需要进行身份验证,通常意味着您需要一个帐户和一个令牌,可以将其与您的请求一起发送。对于 Python Package Tracker,我们需要从 PyPI 获取数据,以获取有关特定软件包发布的信息。幸运的是,PyPI 的 Web API 不需要任何身份验证,所以我们少了一件事情需要担心。当您查看 PyPI JSON API 文档 时,您会发现只有两个端点,即追加到常见基础 URL pypi.org/pypi
的 URL 片段:
`GET /
project_name
/json GET /project_name
/version
/json
第二个端点为您提供与第一个相同的信息,但仅适用于特定版本。对于 Python Package Tracker,第一个端点就足以获取有关软件包过去发布的详细信息,让我们看看这是如何工作的。在 Python 中,与 Web API 交互的简单方法是使用预安装在 Anaconda 中的 Requests 包。运行以下命令以获取有关 pandas 的 PyPI 数据:
In``[``5``]:``import``requests
In``[``6``]:``response``=``requests``.``get``(``"https://pypi.org/pypi/pandas/json"``)``response``.``status_code
Out[6]: 200
每个响应都带有 HTTP 状态码:例如,200
表示 OK,404
表示未找到。您可以在 Mozilla Web 文档 中查找完整的 HTTP 响应状态代码列表。您可能熟悉状态码 404
,因为有时您的浏览器在单击无效链接或键入不存在的地址时会显示它。类似地,如果您使用不存在于 PyPI 上的软件包名称运行 GET 请求,您也将收到 404
状态码。要查看响应的内容,最简单的方法是调用响应对象的 json
方法,它将把响应的 JSON 字符串转换为 Python 字典:
In``[``7``]:``response``.``json``()
- 响应非常长,因此我只打印了一小部分以便您了解结构:
Out[7]: { 'info': { 'bugtrack_url': None, 'license': 'BSD', 'maintainer': 'The PyData Development Team', 'maintainer_email': 'pydata@googlegroups.com', 'name': 'pandas' }, 'releases': { '0.1': [ { 'filename': 'pandas-0.1.tar.gz', 'size': 238458, 'upload_time': '2009-12-25T23:58:31' }, { 'filename': 'pandas-0.1.win32-py2.5.exe', 'size': 313639, 'upload_time': '2009-12-26T17:14:35' } ] } }
要获取所有发布和它们的日期列表,这是 Python 包追踪器所需的内容,我们可以运行以下代码来遍历releases
字典:
In``[``8``]:``releases``=``[]``for``version``,``files``in``response``.``json``()[``'releases'``]``.``items``():``releases``.``append``(``f``"{version}: {files[0]['upload_time']}"``)``releases``[:``3``]``# 显示列表的前 3 个元素
Out[8]: ['0.1: 2009-12-25T23:58:31', '0.10.0: 2012-12-17T16:52:06', '0.10.1: 2013-01-22T05:22:09']
请注意,我们是从列表中首先出现的包中随意选择发布时间戳。一个特定的发布版本通常有多个包,以适应不同版本的 Python 和操作系统。总结这个话题时,您可能会记得第五章中提到 pandas 具有一个read_json
方法,可以直接从 JSON 字符串返回 DataFrame。然而,在这里它对我们没有帮助,因为 PyPI 的响应不是一个可以直接转换为 DataFrame 的结构。
这是一个关于 Web API 的简短介绍,以便理解它们在 Python 包追踪器的代码库中的使用。现在让我们看看如何与数据库进行通信,这是我们应用程序中使用的另一个外部系统!
数据库
即使在没有连接到互联网时,也可以使用 PyPI 数据,您需要在下载后进行存储才能使用。虽然您可以将 JSON 响应存储为磁盘上的文本文件,但更舒适的解决方案是使用数据库:这使您可以轻松地查询数据。Python 包追踪器正在使用SQLite,一个关系数据库。关系数据库系统得名于关系,它指的是数据库表本身(而不是表之间的关系,这是一个常见的误解):它们的最高目标是数据完整性,通过将数据分割成不同的表(这个过程称为规范化)并应用约束条件来避免不一致和冗余数据。关系数据库使用 SQL(结构化查询语言)执行数据库查询,并且是最受欢迎的基于服务器的关系数据库系统之一有SQL Server,Oracle,PostgreSQL和MySQL。作为 Excel 用户,您可能也熟悉基于文件的Microsoft Access数据库。
NOSQL 数据库
如今,关系数据库面临着来自 NoSQL 数据库的强大竞争,后者存储冗余数据以实现以下优势:
无需表联接
由于关系数据库将它们的数据分布在多个表中,您通常需要通过联接这些表来合并两个或多个表中的信息,这可能会导致速度较慢。对于 NoSQL 数据库来说这不是必需的,它可以在某些类型的查询中实现更好的性能。
无数据库迁移
对于关系型数据库系统,每次对表结构进行更改(例如向表添加新列),都必须运行数据库迁移。迁移是将数据库带入所需新结构的脚本。这使得应用程序的新版本部署更加复杂,可能导致停机,这是使用 NoSQL 数据库更容易避免的。
更易于扩展
NoSQL 数据库更容易在多个服务器上分发,因为没有依赖于彼此的表。这意味着使用 NoSQL 数据库的应用程序在用户基数激增时可能更好地扩展。
NoSQL 数据库有多种类型:有些数据库是简单的键值存储,即类似于 Python 中的字典(例如,Redis);其他允许以文档形式存储数据,通常以 JSON 格式(例如,MongoDB)。一些数据库甚至结合了关系型和 NoSQL 的特性:PostgreSQL 是 Python 社区中最流行的数据库之一,传统上是关系型数据库,但也允许您以 JSON 格式存储数据——而不会失去通过 SQL 查询的能力。
SQLite 是我们将要使用的数据库,类似于 Microsoft Access 的基于文件的数据库。不过,与仅在 Windows 上运行的 Microsoft Access 不同,SQLite 可以在 Python 支持的所有平台上运行。另一方面,SQLite 不允许您构建像 Microsoft Access 那样的用户界面,但是 Excel 在此部分非常方便。
现在让我们来看看在找出如何使用 Python 连接到数据库并进行 SQL 查询之前,Package Tracker 数据库的结构。接下来,我们将看一下数据库驱动应用程序的一个流行漏洞——SQL 注入,来结束这篇关于数据库的介绍。
Package Tracker 数据库
Python Package Tracker 的数据库非常简单,只有两张表:packages
表存储包名,package_versions
表存储版本字符串和上传日期。这两张表可以通过 package_id
进行连接:package_versions
表中不再重复存储 package_name
,而是通过归一化存储在 packages
表中。这样消除了冗余数据——例如,只需在整个数据库中的一个字段中执行名称更改。如果想更深入了解带有 xlwings 和 pandas 包加载的数据库结构,请参考表 11-1 和 11-2。
表 11-1. packages 表
package_id | package_name |
---|---|
1 |
xlwings |
2 |
pandas |
表 11-2. package_versions 表(每个 package_id 的前三行)
package_id | version_string | uploaded_at |
---|---|---|
1 |
0.1.0 |
2014-03-19 18:18:49.000000 |
1 |
0.1.1 |
2014-06-27 16:26:36.000000 |
1 |
0.2.0 |
2014-07-29 17:14:22.000000 |
... |
... |
... |
2 |
0.1 |
2009-12-25 23:58:31.000000 |
2 |
0.2beta |
2010-05-18 15:05:11.000000 |
2 |
0.2b1 |
2010-05-18 15:09:05.000000 |
... |
... |
... |
图 11-3 是一个数据库图示,再次以示意方式显示了两个表。你可以查阅表和列名,并获取有关主键和外键的信息:
主键
关系数据库要求每个表都有一个主键。主键是唯一标识行(行也称为记录)的一个或多个列。在
packages
表中,主键是package_id
;在package_versions
表中,主键是所谓的复合键,即package_id
和version_string
的组合。
外键
package_versions
表中的package_id
列是对packages
表中同一列的外键,由连接表的线条来象征:外键是一个约束条件,在我们的情况下,确保package_versions
表中的每个package_id
都存在于packages
表中——这保证了数据的完整性。连接线的右端分支象征着关系的性质:一个package
可以有多个package_versions
,这被称为一对多关系。
图 11-3. 数据库图示(主键用粗体表示)
要查看数据库表的内容并运行 SQL 查询,可以安装一个名为 SQLite 的 VS Code 扩展(请参阅 SQLite 扩展文档 获取更多详细信息),或者使用专用的 SQLite 管理软件,种类繁多。不过,我们将使用 Python 来运行 SQL 查询。在做任何事情之前,让我们看看如何连接到数据库!
数据库连接
要从 Python 连接到数据库,需要一个驱动程序,即一个知道如何与你使用的数据库通信的 Python 包。每个数据库都需要不同的驱动程序,每个驱动程序使用不同的语法,但幸运的是,有一个强大的包叫做 SQLAlchemy,它抽象了各种数据库和驱动程序之间的大部分差异。SQLAlchemy 主要用作对象关系映射器(ORM),将你的数据库记录转换为 Python 对象,这个概念对许多开发者来说更自然,尽管并非所有人都是这么认为的。为了简化问题,我们忽略了 ORM 功能,只使用 SQLAlchemy 来更轻松地运行原始 SQL 查询。当你使用 pandas 以 DataFrames 的形式读取和写入数据库表时,SQLAlchemy 也是在幕后使用的。从 pandas 运行数据库查询涉及三个层次的包—— pandas、SQLAlchemy 和数据库驱动程序——如 图 11-4 所示。你可以从这三个层次运行数据库查询。
图 11-4. 从 Python 访问数据库
表 11-3 展示了 SQLAlchemy 默认使用的驱动程序(某些数据库可以使用多个驱动程序)。它还为你提供了数据库连接字符串的格式——稍后我们将在实际运行 SQL 查询时使用这个连接字符串。
表 11-3. SQLAlchemy 默认驱动程序和连接字符串
数据库 | 默认驱动程序 | 连接字符串 |
---|---|---|
SQLite | sqlite3 |
sqlite:///``文件路径 |
PostgreSQL | psycopg2 |
postgresql://``用户名``:``密码``@``主机``:``端口``/``数据库 |
MySQL | mysql-python |
mysql://``用户名``:``密码``@``主机``:``端口``/``数据库 |
Oracle | cx_oracle |
oracle://``用户名``:``密码``@``主机``:``端口``/``数据库 |
SQL Server | pyodbc |
mssql+pyodbc://``用户名``:``密码``@``主机``:``端口``/``数据库 |
除了 SQLite,通常需要密码才能连接到数据库。由于连接字符串是 URL,如果密码中包含特殊字符,你将需要使用 URL 编码的版本。以下是如何打印密码的 URL 编码版本:
In``[``9``]:``import``urllib.parse
In``[``10``]:``urllib``.``parse``.``quote_plus``(``"pa$$word"``)
Out[10]: 'pa%24%24word'
在介绍了 pandas、SQLAlchemy 和数据库驱动程序作为连接数据库的三个层级后,让我们看看它们在实践中如何比较,通过几个 SQL 查询来演示!
SQL 查询
即使你对 SQL 还很陌生,你也应该没有问题理解我接下来在示例和 Python Package Tracker 中使用的几个 SQL 查询。SQL 是一种声明式语言,这意味着你告诉数据库你想要什么而不是怎么做。有些查询几乎读起来像是普通的英语:
SELECT``*``FROM``packages
这告诉数据库你要从 packages 表中选择所有列。在生产代码中,你不应该使用通配符 *
来表示所有列,而是明确指定每一列,这样可以减少查询错误的可能性:
SELECT``package_id``,``package_name``FROM``packages
数据库查询 vs. Pandas DataFrames
SQL 是一种基于集合的语言,这意味着你操作的是一组行而不是逐行循环。这与你处理 pandas DataFrames 的方式非常相似。以下是 SQL 查询:
SELECT``package_id``,``package_name``FROM``packages
对应以下 pandas 表达式(假设
packages
是一个 DataFrame):
packages``.``loc``[:,``[``"package_id"``,``"package_name"``]]
下面的代码示例使用的是 packagetracker 文件夹中的 packagetracker.db 文件,您可以在伴随存储库的 packagetracker 文件夹中找到它。示例假设您已经通过 Python Package Tracker 的 Excel 前端将 xlwings 和 pandas 添加到了数据库中,就像我们在本章的开头所做的那样——否则您将只得到空结果。根据图 11-4,从底部到顶部,我们首先直接从驱动程序执行我们的 SQL 查询,然后使用 SQLAlchemy,最后使用 pandas:
In``[``11``]:``# 让我们从导入开始``import``sqlite3``from``sqlalchemy``import``create_engine``import``pandas``as``pd
In``[``12``]:``# 我们的 SQL 查询:"从 packages 表中选择所有列"``sql``=``"SELECT * FROM packages"
In``[``13``]:``# 选项 1: 数据库驱动程序(sqlite3 是标准库的一部分)``# 使用连接作为上下文管理器自动提交事务或在错误的情况下回滚。``with``sqlite3``.``connect``(``"packagetracker/packagetracker.db"``)``as``con``:``cursor``=``con``.``cursor``()``# 我们需要一个游标来运行 SQL 查询``result``=``cursor``.``execute``(``sql``)``.``fetchall``()``# 返回所有记录``result
Out[13]: [(1, 'xlwings'), (2, 'pandas')]
In``[``14``]:``# 选项 2: SQLAlchemy``# "create_engine"期望你的数据库连接字符串。``# 在这里,我们可以将查询作为连接对象的方法执行。``engine``=``create_engine``(``"sqlite:///packagetracker/packagetracker.db"``)``with``engine``.``connect``()``as``con``:``result``=``con``.``execute``(``sql``)``.``fetchall``()``result
Out[14]: [(1, 'xlwings'), (2, 'pandas')]
In``[``15``]:``# 选项 3: pandas``# 向"read_sql"提供表名读取完整的表。``# Pandas 需要一个 SQLAlchemy 引擎,我们从前面的示例中重用。``df``=``pd``.``read_sql``(``"packages"``,``engine``,``index_col``=``"package_id"``)``df
Out[15]: package_name package_id 1 xlwings 2 pandas
In``[``16``]:``# "read_sql"还接受一个 SQL 查询``pd``.``read_sql``(``sql``,``engine``,``index_col``=``"package_id"``)
Out[16]: package_name package_id 1 xlwings 2 pandas
In``[``17``]:``# DataFrame 方法"to_sql"将 DataFrame 写入表中``# "if_exists"必须是"fail"、"append"或"replace"之一``df``.``to_sql``(``"packages2"``,``con``=``engine``,``if_exists``=``"append"``)
In``[``18``]:``# 前面的命令创建了一个名为"packages2"的新表,并将 DataFrame df 中的记录插入其中,我们可以通过重新读取来验证``pd``.``read_sql``(``"packages2"``,``engine``,``index_col``=``"package_id"``)
Out[18]: package_name package_id 1 xlwings 2 pandas
In``[``19``]:``# 让我们再次通过运行 SQLAlchemy 的“drop table”命令来摆脱这个表``with``engine``.``connect``()``as``con``:``con``.``execute``(``"DROP TABLE packages2"``)
是否应该使用数据库驱动程序 SQLAlchemy 或 pandas 来运行您的查询,很大程度上取决于您的偏好:我个人喜欢使用 SQLAlchemy 获得的精细控制,并且享受使用相同语法在不同数据库上的便利。另一方面,pandas 的read_sql
便于将查询结果以 DataFrame 的形式获取。
使用 SQLITE 的外键
有些令人惊讶的是,默认情况下,SQLite 在运行查询时不会默认遵守外键约束。但是,如果您使用 SQLAlchemy,可以轻松强制执行外键约束;请参阅SQLAlchemy 文档。如果您从 pandas 运行查询,这也适用。您将在伴随存储库的 packagetracker 文件夹的 database.py 模块顶部找到相应的代码。
现在您知道如何运行简单的 SQL 查询了,让我们通过查看 SQL 注入来结束本节,这可能会对您的应用程序构成安全风险。
SQL 注入
如果您没有正确保护 SQL 查询,恶意用户可以通过向数据输入字段注入 SQL 语句来运行任意的 SQL 代码:例如,在 Python 包追踪器的下拉菜单中选择包名 xlwings 时,他们可以发送一个改变您预期查询的 SQL 语句。这可能会暴露敏感信息或执行像删除表这样的破坏性操作。如何预防这种情况?让我们首先看一下下面的数据库查询,当您选择 xlwings 并点击“显示历史记录”时,包追踪器运行的查询:1
SELECT``v``.``uploaded_at``,``v``.``version_string``FROM``packages``p``INNER``JOIN``package_versions``v``ON``p``.``package_id``=``v``.``package_id``WHERE``p``.``package_id``=``1
此查询将两个表连接在一起,仅返回package_id
为1
的行。为了帮助您基于我们在第五章中学到的内容理解此查询,如果packages
和package_versions
是 pandas 的 DataFrame,您可以编写:
df``=``packages``.``merge``(``package_versions``,``how``=``"inner"``,``on``=``"package_id"``)``df``.``loc``[``df``[``"package_id"``]``==``1``,``[``"uploaded_at"``,``"version_string"``]]
很明显,package_id
需要是一个变量,而不是我们现在硬编码的1
,以便根据所选包返回正确的行。了解到 f-strings 来自第三章,您可能会想要像这样更改 SQL 查询的最后一行:
f``"WHERE p.package_id = {package_id}"
尽管这在技术上可以工作,但绝不能这样做,因为它会为 SQL 注入打开大门:例如,有人可以发送 '1 OR TRUE'
而不是表示 package_id
的整数。生成的查询将返回整个表的行,而不仅仅是 package_id
等于 1
的行。因此,始终使用 SQLAlchemy 为您提供的占位符语法(它们以冒号开头):
In``[``20``]:``# 让我们从导入 SQLAlchemy 的 text 函数开始``从``sqlalchemy.sql``导入``text
In``[``21``]:``# “:package_id” 是占位符``sql``=``"""`` 选择``v.uploaded_at,``v.version_string`` 从``packages``p`` 内连接``package_versions``v``在``p.package_id``=``v.package_id`` 其中``p.package_id``=``:package_id`` 按``v.uploaded_at``排序``"""
In``[``22``]:``# 通过 SQLAlchemy``使用``engine``.``connect``()``连接``的``方式``获取``结果``=``con``.``execute``(``text``(``sql``),``package_id``=``1``)``.``fetchall``()``结果``[:``3``]``# 打印``前``3``条记录
Out[22]: [('2014-03-19 18:18:49.000000', '0.1.0'), ('2014-06-27 16:26:36.000000', '0.1.1'), ('2014-07-29 17:14:22.000000', '0.2.0')]
In``[``23``]:``# 通过 pandas``pd``.``read_sql``(``text``(``sql``),``engine``,``parse_dates``=``[``"uploaded_at"``],``params``=``{``"package_id"``:``1``},``index_col``=``[``"uploaded_at"``])``.``head``(``3``)
Out[23]: version_string uploaded_at 2014-03-19 18:18:49 0.1.0 2014-06-27 16:26:36 0.1.1 2014-07-29 17:14:22 0.2.0
使用 SQLAlchemy 的 text
函数包装 SQL 查询具有一个优点,即您可以在不同的数据库中使用相同的占位符语法。否则,您将不得不使用数据库驱动程序使用的占位符:例如,sqlite3
使用 ?
而 psycopg2
使用 %s
。
您可能会认为当用户直接访问 Python 并可以在数据库上运行任意代码时,SQL 注入并不是什么问题。但是,如果有一天将您的 xlwings 原型转换为 Web 应用程序,这将成为一个巨大的问题,因此最好从一开始就做好。
除了 Web API 和数据库,我们到目前为止跳过的另一个不可或缺的主题是坚实应用程序开发的异常处理。让我们看看它是如何工作的!
异常
我在第一章中提到了异常处理作为 VBA 在其 GoTo 机制方面落后的一个例子。在本节中,我将向您展示 Python 如何使用 try/except 机制来处理程序中的错误。每当某些事情超出您的控制时,错误可能会发生。例如,当您尝试发送电子邮件时,可能会出现电子邮件服务器宕机的情况,或者您的程序期望的文件可能会丢失-例如 Python Package Tracker 中可能是数据库文件。处理用户输入是另一个需要准备应对不合理输入的领域。让我们练习一下-如果以下函数被零调用,您将得到ZeroDivisionError
:
In``[``24``]:
定义print_reciprocal``(``number``):
结果=
1/
number打印
(f
"The reciprocal is: {result}"``)`
In``[``25``]:
print_reciprocal(
0)
#这将引发错误`
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-25-095f19ebb9e9> in <module> ----> 1
print_reciprocal(
0) #这将引发错误 <ipython-input-24-88fdfd8a4711> in `print_reciprocal
(number
) 1定义
print_reciprocal(
number): ----> 2 `结果
=1
/number
3打印``(``f``"The reciprocal is: {result}"``) ZeroDivisionError: division by zero
要使您的程序能够优雅地对此类错误做出反应,请使用 try/except 语句(这相当于第一章中 VBA 示例的等价物):
In``[``26``]:
定义print_reciprocal``(``number``):
尝试:`结果
=1
/number
除Exception
ase
:#"as e"使异常对象可用作变量"e"
#"repr"表示对象的可打印表示#并返回一个带有错误消息的字符串
打印(
f"There was an error: {repr(e)}"
)结果
="N/A"
else:
打印(
"There was no error!")
最后:
打印(
f"The reciprocal is: {result}"
)`
每当try
块中发生错误时,代码执行将移至except
块,您可以在那里处理错误:这使您能够为用户提供有用的反馈或将错误写入日志文件。else
子句仅在try
块中未引发错误并且finally
块始终运行时运行,无论是否引发错误。通常情况下,您只需使用try
和except
块。让我们看看给定不同输入时函数的输出:
In``[``27``]:
print_reciprocal(
10``)`
There was no error! The reciprocal is: 0.1
In``[``28``]:
print_reciprocal(
"a"``)`
There was an error: TypeError("unsupported operand type(s) for /: 'int' and 'str'") The reciprocal is: N/A
In``[``29``]:
print_reciprocal(
0``)`
There was an error: ZeroDivisionError('division by zero') The reciprocal is: N/A
The way that I have used the except statement means that any exception that happens in the try
block will cause the code execution to continue in the except
block. Usually, that is not what you want. You want to check for an error as specific as possible and handle only those you expect. Your program may otherwise fail for something completely unexpected, which makes it hard to debug. To fix this, rewrite the function as follows, checking explicitly for the two errors that we expect (I am leaving away the else
and finally
statements):
In``[``30``]:``def``print_reciprocal``(``number``):``try``:``result``=``1``/``number``print``(``f``"The reciprocal is: {result}"``)``except``(``TypeError``,``ZeroDivisionError``):``print``(``"Please type in any number except 0."``)
让我们再次运行代码:
In``[``31``]:``print_reciprocal``(``"a"``)
请键入除 0 以外的任何数字。
如果你想根据异常不同处理错误,应分别处理它们:
In``[``32``]:``def``print_reciprocal``(``number``):``try``:``result``=``1``/``number``print``(``f``"The reciprocal is: {result}"``)``except``TypeError``:``print``(``"Please type in a number."``)``except``ZeroDivisionError``:``print``(``"The reciprocal of 0 is not defined."``)
In``[``33``]:``print_reciprocal``(``"a"``)
请键入一个数字。
In``[``34``]:``print_reciprocal``(``0``)
The reciprocal of 0 is not defined.
现在你了解了错误处理、Web API 和数据库,可以继续学习下一节内容,我们将逐个讲解 Python Package Tracker 的每个组成部分。
应用程序结构
在本节中,我们将深入了解 Python Package Tracker 的幕后工作原理。首先,我们将详细介绍应用程序的前端,即 Excel 文件,然后再看其后端,即 Python 代码。最后,我们将了解 xlwings 项目的调试方法,这对于像 Package Tracker 这样规模和复杂性的项目非常有用。
在 companion repo 的 packagetracker 目录中,你会找到四个文件。还记得我在第一章中提到的关注点分离吗?现在我们可以将这些文件映射到如表格 11-4 所示的不同层次。
表格 11-4. 关注点分离
层次 | 文件 | 描述 |
---|---|---|
展示层 | packagetracker.xlsm |
这是前端,也是最终用户与之交互的唯一文件。 |
业务层 | packagetracker.py |
该模块通过 Web API 处理数据下载,并使用 pandas 进行数据处理。 |
数据层 | database.py |
该模块处理所有数据库查询。 |
数据库 | packagetracker.db |
这是一个 SQLite 数据库文件。 |
在这个上下文中,值得一提的是,即 Excel 文件的展示层并不包含单个单元格公式,这使得工具更容易审计和控制。
模型-视图-控制器(MVC)
关注分离有多种形式,如 表 11-4 所示的分解只是一种可能性。您可能很快就会遇到的另一种流行设计模式称为模型-视图-控制器(MVC)。在 MVC 的世界中,应用程序的核心是模型,其中处理所有数据和通常大部分业务逻辑。视图对应于表示层,控制器只是一个薄层,位于模型和视图之间,以确保它们始终保持同步。为了简化事务,本书未使用 MVC 模式。
现在您知道每个文件负责什么,让我们继续深入了解 Excel 前端的设置方式!
前端
当您构建 Web 应用程序时,您区分前端和后端。前端是在浏览器中运行的应用程序部分,而后端是在服务器上运行的代码,您通过RunPython
调用它。我们可以将相同的术语应用于 xlwings 工具:前端是 Excel 文件,后端是 Python 代码,您通过 Anaconda Prompt 运行以下命令(确保首先cd
到您选择的目录):
(base)>
xlwings quickstart packagetracker
转到 packagetracker 目录,并在 Excel 中打开 packagetracker.xlsm。首先添加三个选项卡:Tracker、Database 和 Dropdown,如 图 11-5 所示。
图 11-5. 构建用户界面
虽然您应该能够接管来自 图 11-5 的文本和格式,但我需要给您一些关于不可见事物的详细信息:
按钮
为了使工具看起来不那么像 Windows 3.1,我没有使用我们在前一章中使用的标准宏按钮。相反,我去插入 > 形状,插入了一个圆角矩形。如果您想使用标准按钮,那也可以,但在这一点上,请不要立即分配宏。
命名区域
为了使工具更易于维护,我们将在 Python 代码中使用命名区域而不是单元格地址。因此,请按照 表 11-5 所示添加命名区域。
表 11-5. 命名区域
表格 | 单元格 | 名称 |
---|
|
跟踪器
|
B5
|
package_selection
|
|
跟踪器
|
B11
|
latest_release
|
|
数据库
|
B5
|
new_package
|
|
数据库
|
B13
|
updated_at
|
|
数据库
|
B18
|
log
|
添加命名区域的一种方法是选择单元格,然后在名称框中编写名称,最后按 Enter 确认,如 图 11-6 所示。
图 11-6. 名称框
表格
在 Dropdown 表上,在单元格 A1 中键入“packages”后,选择 A1,然后转到 插入 > 表格,并确保激活“我的表格具有标题”旁边的复选框。最后,选中表格后,转到功能区标签 表格设计(Windows)或表格(macOS),并将表格从
Table1
重命名为dropdown_content
,如图 Figure 11-7 所示。
图 Figure 11-7。重命名 Excel 表格
数据验证
我们使用数据验证在 Tracker 表的 B5 单元格中提供下拉菜单。要添加它,请选择单元格 B5,然后转到 数据 > 数据验证,允许下拉菜单,然后在源中设置以下公式:
=INDIRECT("dropdown_content[packages]")
然后,点击确认。这只是表的一个参考,但由于 Excel 不能直接接受表引用,我们必须用
INDIRECT
公式将其包装起来,这将表解析为其地址。然而,通过使用表,当我们添加更多包时,它会适当调整下拉菜单中显示的范围。
条件格式化
当您添加一个包时,可能会出现几个错误需要向用户显示:字段可能为空,包可能已经存在于数据库中,或者可能在 PyPI 上缺失。为了在红色中显示错误信息和其他消息为黑色,我们将使用基于条件格式化的简单技巧:每当消息包含
error
时,我们希望字体变为红色。在数据库表中,选择单元格 C5,这是我们将写出消息的地方。然后进入主页 > 条件格式化 > 突出显示单元格规则 > 包含文本。输入值error
并在下拉菜单中选择红色文本,如图 Figure 11-8 所示,然后点击确定。将同样的条件格式应用到 Tracker 表的单元格 C5 上。
图 Figure 11-8。Windows 上的条件格式化(左)和 macOS 上的条件格式化(右)
网格线
在 Tracker 和 Database 表上,网格线已通过在页面布局 > 网格线 下取消勾选查看复选框而隐藏。
此时,用户界面已完成,应如图 Figure 11-5 所示。现在我们需要在 VBA 编辑器中添加 RunPython
调用,并将它们与按钮连接起来。点击 Alt+F11(Windows)或 Option-F11(macOS)打开 VBA 编辑器,然后,在 packagetracker.xlsm 的 VBAProject 下,双击左侧的 Modules 下的 Module1 打开它。删除现有的 SampleCall
代码,并替换为以下宏:
Sub``AddPackage``()``RunPython``"import packagetracker; packagetracker.add_package()"``End``Sub``Sub``ShowHistory``()``RunPython``"import packagetracker; packagetracker.show_history()"``End``Sub``Sub``UpdateDatabase``()``RunPython``"import packagetracker; packagetracker.update_database()"``End``Sub
接下来,右键单击每个按钮,选择分配宏,并选择与按钮对应的宏。图 11-9 显示了“显示历史记录”按钮,但对于“添加包”和“更新数据库”按钮,操作方式相同。
图 11-9. 将“显示历史记录”宏分配给“显示历史记录”按钮
前端部分已完成,我们可以继续进行 Python 后端的开发。
后端
packagetracker.py 和 database.py 两个 Python 文件的代码太长,无法在此处显示,因此您需要从配套存储库中在 VS Code 中打开它们。不过,在本节中我会引用一些代码片段来解释一些关键概念。让我们看看当您在 Database 表单上单击“添加包”按钮时会发生什么。该按钮分配了以下 VBA 宏:
Sub``AddPackage``()``RunPython``"import packagetracker; packagetracker.add_package()"``End``Sub
如您所见,RunPython
函数调用了 packagetracker
模块中的 add_package
Python 函数,如 示例 11-1 中所示。
无生产代码
为了便于跟踪,应用尽可能简化—它并不检查所有可能出错的事情。在生产环境中,您希望使其更加健壮:例如,如果找不到数据库文件,您将显示用户友好的错误信息。
示例 11-1. packagetracker.py 中的 add_package
函数(无注释)
def``add_package``():``db_sheet``=``xw``.``Book``.``caller``()``.``sheets``[``"Database"``]``package_name``=``db_sheet``[``"new_package"``]``.``value``feedback_cell``=``db_sheet``[``"new_package"``]``.``offset``(``column_offset``=``1``)``feedback_cell``.``clear_contents``()``if``not``package_name``:``feedback_cell``.``value``=``"错误:请提供名称!"
return``if``requests``.``get``(``f``"{BASE_URL}/{package_name}/json"``,``timeout``=``6``)``.``status_code``!=``200``:
feedback_cell``.``value``=``"错误:未找到包!"``return``error``=``database``.``store_package``(``package_name``)
db_sheet``[``"new_package"``]``.``clear_contents``()``if``error``:``feedback_cell``.``value``=``f``"错误:{error}"``else``:``feedback_cell``.``value``=``f``"成功添加 {package_name}。"``update_database``()
refresh_dropdown``()
通过条件格式化,反馈消息中的“error”将触发 Excel 中的红色字体。
默认情况下,Requests 会永久等待响应,这可能导致应用在 PyPI 出现问题且响应速度慢时“挂起”。因此,对于生产代码,您应始终包含显式的
timeout
参数。
如果操作成功,
store_package
函数返回None
,否则返回包含错误消息的字符串。
为了简化操作,整个数据库都会被更新。在生产环境中,你只会添加新包裹的记录。
这将更新 Dropdown 表中的表格内容到
packages
表格。结合在 Excel 中设置的数据验证,这确保了所有包裹出现在 Tracker 表单的下拉列表中。如果你允许数据库从 Excel 文件外部填充,你需要为用户提供直接调用该函数的方法。一旦你有多个用户从不同的 Excel 文件访问同一个数据库,这种情况就会发生。
你应该能够通过代码注释来理解 packagetracker.py 文件中的其他功能。现在让我们转向 database.py 文件。首几行如示例 11-2 所示。
示例 11-2. database.py(包含相关导入的摘录)
from``pathlib``import``Path``import``sqlalchemy``import``pandas``as``pd``...``# 我们希望数据库文件与此文件同级。``# 这里,我们将路径转换为绝对路径。``this_dir``=``Path``(``__file__``)``.``resolve``()``.``parent
db_path``=``this_dir``/``"packagetracker.db"``# 数据库引擎``engine``=``sqlalchemy``.``create_engine``(``f``"sqlite:///{db_path}"``)
如果你需要对这行代码的作用进行复习,请参考第七章开头的内容,在销售报告的代码中我有解释。
虽然这个片段涉及组合数据库文件路径,但它还展示了如何解决你在处理任何文件时可能遇到的常见错误,无论是图片、CSV 文件还是像这种情况下的数据库文件。当你快速编写 Python 脚本时,你可能只使用相对路径,就像我在大多数 Jupyter 笔记本示例中所做的那样:
engine``=``sqlalchemy``.``create_engine``(``"sqlite:///packagetracker.db"``)
只要文件在你的工作目录中,这就可以工作。但是,当你通过RunPython
从 Excel 运行这段代码时,工作目录可能会不同,这会导致 Python 在错误的文件夹中查找文件,你将会得到一个File not found
错误。你可以通过提供绝对路径或者像我们在示例 11-2 中那样创建路径来解决这个问题。这确保了即使你通过RunPython
从 Excel 执行代码,Python 也会在与源文件相同的目录中查找文件。
如果你想从头开始创建 Python 包跟踪器,你需要手动创建数据库:例如通过在 VS Code 中点击运行文件按钮来运行 database.py 文件。这将创建数据库文件 packagetracker.db 并包含两个表格。创建数据库的代码位于 database.py 的最底部:
if
__name__
==``"__main__"``:``create_db``()
尽管最后一行调用了create_db
函数,但前面的if
语句的含义在以下的提示中有解释。
IF NAME == “MAIN”
您会在许多 Python 文件的底部看到这个
if
语句。它确保当您以脚本的方式运行代码时才会执行,例如,通过在 Anaconda Prompt 中运行python database.py
或在 VS Code 中点击运行文件按钮。但是,当您通过import database
在您的代码中导入它时,它不会被触发。这是因为 Python 在直接作为脚本运行时将文件命名为__main__
,而在通过import
语句调用时将其称为模块名(database
)。由于 Python 在一个名为__name__
的变量中跟踪文件名,所以if
语句只在以脚本方式运行时评估为True
;当您通过在 packagetracker.py 文件中导入它时,它不会被触发。
database
模块的其余部分通过 SQLAlchemy 和 pandas 的to_sql
和read_sql
方法运行 SQL 语句,这样您可以了解这两种方法的使用感觉。
迁移到 POSTGRESQL
如果您想用基于服务器的数据库 POSTGRESQL 替换 SQLite,您只需做几件事情。首先,您需要运行
conda install psycopg2
(或者如果您不使用 Anaconda 发行版,则运行pip install psycopg2-binary
)来安装 POSTGRESQL 驱动程序。然后,在 database.py 中,根据 Table 11-3 中显示的 POSTGRESQL 版本更改create_engine
函数中的连接字符串。最后,要创建表,您需要将packages.package_id
的INTEGER
数据类型更改为 POSTGRESQL 的特定表示法SERIAL
。创建自增主键是 SQL 方言之间差异的一个示例。
当您创建像 Python 包追踪器这样复杂的工具时,您可能会遇到一些问题:例如,您可能已经在 Excel 中重命名了一个命名范围,但忘记相应地调整 Python 代码。这是了解调试工作原理的好时机!
调试
为了方便调试您的 xlwings 脚本,请直接从 VS Code 运行您的函数,而不是通过在 Excel 中点击按钮运行它们。在 packagetracker.py 文件的底部添加以下几行代码,这将帮助您调试add_package
函数(这段代码也可以在quickstart
项目的底部找到):
if
__name__
==``"__main__"``:
xw``.``Book``(``"packagetracker.xlsm"``)``.``set_mock_caller``()
add_package``()
我们刚刚看到了这个 if 语句在查看 database.py 代码时的工作原理;请参见前面的提示。
当您直接从 Python 作为脚本运行文件时,
set_mock_caller()
命令仅用于调试目的:当您在 VS Code 中或从 Anaconda 提示符中运行文件时,它会将xw.Book.caller()
设置为xw.Book("packagetracker.xlsm")
。这样做的唯一目的是能够在 Python 和 Excel 两端运行脚本,而无需在add_package
函数中来回切换xw.Book("packagetracker.xlsm")
(从 VS Code 调用时)和xw.Book.caller()
(从 Excel 调用时)的书对象。
在 VS Code 中打开 packagetracker.py,在 add_package
函数的任何行左侧的行号单击设置断点。然后按 F5 并在对话框中选择“Python 文件”以启动调试器,并使您的代码停在断点处。确保使用 F5 键而不是使用“运行文件”按钮,因为“运行文件”按钮会忽略断点。
调试 VS Code 和 Anaconda
在 Windows 上,第一次使用 VS Code 调试器运行使用 pandas 的代码时,您可能会遇到错误:“异常发生:ImportError,无法导入所需的依赖项:numpy。”这是因为调试器在 Conda 环境正确激活之前已经启动和运行了。作为解决方法,点击停止图标停止调试器,然后再次按 F5——第二次运行时将正常工作。
如果您不熟悉 VS Code 调试器的工作原理,请参阅附录 B,我在那里解释了所有相关功能和按钮。我们也将在下一章的相应部分再次讨论这个主题。如果您想调试不同的函数,请停止当前的调试会话,然后在文件底部调整函数名称。例如,要调试 show_history
函数,请在 packagetracker.py 的最后一行修改如下,然后再次按 F5:
if
__name__
`=="__main__"
:xw
.Book
("packagetracker.xlsm"
).
set_mock_caller()
show_history``()
在 Windows 上,您还可以在 xlwings 插件中激活“显示控制台”复选框,这将在 RunPython
调用运行时显示命令提示符。这允许您打印附加信息以帮助调试问题。例如,您可以打印变量的值以在命令提示符上进行检查。然而,代码运行后,命令提示符将会关闭。如果您需要保持它稍长时间打开,有一个简单的技巧:在函数的最后一行添加 input()
。这会导致 Python 等待用户输入,而不是立即关闭命令提示符。当您完成检查输出时,在命令提示符中按 Enter 关闭它——只需确保在取消选中“显示控制台”选项之前再次删除 input()
行!
结论
这一章节向您展示了,通过最小的努力就能构建相当复杂的应用程序是可能的。能够利用强大的 Python 包如 Requests 或 SQLAlchemy 对我而言意义重大,特别是当我将其与 VBA 相比较时,在 VBA 中与外部系统通信要困难得多。如果您有类似的用例,我强烈建议您更仔细地了解 Requests 和 SQLAlchemy——能够高效处理外部数据源将使您能够让复制/粘贴成为历史。
有些用户更喜欢通过使用单元格公式来创建他们的 Excel 工具,而不是点击按钮。下一章将向您展示 xlwings 如何使您能够在 Python 中编写用户定义函数,从而使您能够重复使用我们迄今学到的大部分 xlwings 概念。
1 实际上,该工具使用
package_name
而不是package_id
来简化代码。2 在撰写本文时,此选项在 macOS 上尚不可用。
第十二章:用户定义函数(UDFs)
过去的三章向你展示了如何使用 Python 脚本自动化 Excel,以及如何在 Excel 中点击按钮运行这样的脚本。本章介绍了另一种选项,即使用 xlwings 从 Excel 中调用 Python 代码的用户定义函数(UDFs)。UDFs 是你在 Excel 单元格中使用的 Python 函数,与像SUM
或AVERAGE
等内置函数的使用方式相同。与上一章类似,我们将从quickstart
命令开始,这样可以很快尝试第一个 UDF。接下来,我们将进行一个关于从 Google Trends 获取和处理数据的案例研究,作为使用更复杂的 UDFs 的借口:我们将学习如何处理 pandas 的数据框架和绘图,以及如何调试 UDFs。最后,我们将深入探讨一些重点放在性能上的高级主题。不幸的是,xlwings 不支持在 macOS 上使用 UDFs,这使得本章是唯一需要在 Windows 上运行示例的章节。1
针对 macOS 和 Linux 用户的注意事项
即使你不在 Windows 平台上,你仍然可能希望看一看 Google Trends 案例研究,因为你可以轻松地将其调整为在 macOS 上使用
RunPython
调用。你还可以使用来自第八章的写入库之一生成报告,该库甚至在 Linux 上也适用。
开始使用 UDFs
在我们可以使用quickstart
命令运行我们的第一个 UDF 之前,本节将从编写 UDFs 的前提条件开始。为了能够跟随本章的示例,你需要安装 xlwings 插件,并在 Excel 选项中启用“允许访问 VBA 项目对象模型”:
插件
我假设你已经按照第十章中所述安装了 xlwings 插件。虽然这不是硬性要求,但它使得开发变得更加容易,特别是点击“插入函数”按钮。不过,这不是部署所必需的,你可以通过在独立模式下设置工作簿来替代它——详情请参见第十章。
允许访问 VBA 项目对象模型
要编写你的第一个 UDF,你需要更改 Excel 中的一个设置:转到“文件” > “选项” > “信任中心” > “信任中心设置” > “宏设置”,并勾选“允许访问 VBA 项目对象模型”的复选框,就像图 12-1 中一样。这样 xlwings 在你点击插入函数按钮时可以自动将一个 VBA 模块插入到你的工作簿中,我们很快就会看到。由于你只在导入过程中依赖此设置,所以你应该将其视为开发者设置,终端用户无需关心。
图 12-1. 允许访问 VBA 项目对象模型
当这两个前提条件准备就绪时,你就可以运行你的第一个 UDF 了!
UDF 快速入门
和往常一样,最快上手的方法是使用 quickstart
命令。在 Anaconda Prompt 上运行以下命令之前,请确保通过 cd
命令切换到所选目录。例如,如果您在主目录中并且想切换到桌面,请先运行 cd Desktop
:
(base)>
xlwings quickstart first_udf
在文件资源管理器中导航至 first_udf
文件夹,用 Excel 打开 first_udf.xlsm
,用 VS Code 打开 first_udf.py
。然后,在 xlwings 的功能增强插件中,点击“导入函数”按钮。默认情况下,这是一个静默操作,即仅在出现错误时才会显示内容。但是,如果在 Excel 插件中激活“显示控制台”复选框,然后再次点击“导入函数”按钮,会打开一个命令提示符并打印以下内容:
xlwings server running [...] Imported functions from the following modules: first_udf
第一行打印了一些我们可以忽略的细节,重要的部分是一旦打印了这一行,Python 就已经启动了。第二行确认它已正确导入了来自 first_udf
模块的函数。现在在 first_udf.xlsm
的活动工作表的单元格 A1 中键入=hello("xlwings")
,按 Enter 键后,您将看到公式的计算结果如 图 12-2 所示。
图 12-2. first_udf.xlsm
让我们详细分析一下每个部分的工作原理:首先看一下 first_udf.py
中的 hello
函数(示例 12-1),这是我们迄今为止忽略的 quickstart
代码的一部分。
示例 12-1. first_udf.py
(摘录)
import``xlwings``as``xw``@xw.func``def``hello``(``name``):``return``f``"Hello {name}!"
每个标有 @xw.func
的函数都会在您点击 xlwings 增强插件中的“导入函数”时导入到 Excel 中。导入函数会使其在 Excel 中可用,以便您可以在单元格公式中使用它们。稍后我们会详细介绍技术细节。@xw.func
是一个装饰器,这意味着您必须直接放在函数定义的顶部。如果您想了解更多有关装饰器如何工作的信息,请查看侧边栏。
函数装饰器
装饰器是放在函数定义顶部的函数名,以
@
符号开头。这是一种简单的方式来改变函数的行为,xlwings 使用它来识别您想在 Excel 中提供的函数。为了帮助您理解装饰器的工作原理,以下示例显示了一个名为verbose
的装饰器的定义,它会在运行函数print_hello
前后打印一些文本。从技术上讲,装饰器接受函数(print_hello
)并将其作为参数func
提供给verbose
函数。内部函数称为wrapper
,可以执行需要执行的操作;在本例中,它在调用print_hello
函数之前和之后打印一个值。内部函数的名称无关紧要:
In``[``1``]:``# 这是函数装饰器的定义``def``verbose``(``func``):``def``wrapper``():``print``(``"调用函数前。"``)``func``()``print``(``"调用函数后。"``)``return``wrapper
In``[``2``]:``# 使用函数装饰器``@verbose``def``print_hello``():``print``(``"hello!"``)
In``[``3``]:``# 调用装饰函数的效果``print_hello``()
调用函数前。hello! 调用函数后。
在本章末尾,你会找到 表 12-1,其中总结了 xlwings 提供的所有装饰器。
默认情况下,如果函数参数是单元格范围,xlwings 将传递这些单元格范围的值,而不是 xlwings 的 range
对象。在绝大多数情况下,这非常方便,并允许你使用单元格调用 hello
函数。例如,你可以在单元格 A2 中写入“xlwings”,然后将 A1 中的公式更改为以下内容:
=``hello``(``A2``)
结果与 图 12-2 中的相同。我将在本章的最后一节中向你展示如何更改此行为,并使参数作为 xlwings range
对象传递——正如我们之后将看到的,有时你会需要这样做。在 VBA 中,等效的 hello
函数如下所示:
Function``hello``(``name``As``String``)``As``String``hello``=``"Hello "``&``name``&``"!"``End``Function
当你点击附加组件中的导入函数按钮时,xlwings 会将名为 xlwings_udfs
的 VBA 模块插入到你的 Excel 工作簿中。它包含每个你导入的 Python 函数的一个 VBA 函数:这些包装 VBA 函数负责在 Python 中运行相应的函数。虽然你可以通过 Alt+F11 打开 VBA 编辑器查看 xlwings_udfs
VBA 模块,但你可以忽略它,因为代码是自动生成的,当你再次点击导入函数按钮时,任何更改都将丢失。现在让我们在 first_udf.py 中玩弄我们的 hello
函数,并将返回值中的 Hello
替换为 Bye
:
@xw.func``def``hello``(``name``):``return``f``"Bye {name}!"
要在 Excel 中重新计算函数,要么双击单元格 A1 以编辑公式(或者选择单元格并按 F2 激活编辑模式),然后按 Enter。或者,键入键盘快捷键 Ctrl+Alt+F9:这将强制重新计算所有打开工作簿中所有工作表的公式,包括 hello
公式。请注意,F9(重新计算所有打开工作簿中的所有工作表)或 Shift+F9(重新计算活动工作表)不会重新计算 UDF,因为 Excel 仅在依赖单元格更改时触发 UDF 的重新计算。要更改此行为,你可以通过向 func
装饰器添加相应的参数来使函数成为 volatile:
@xw.func``(``volatile``=``True``)``def``hello``(``name``):``return``f``"Bye {name}!"
Volatile 函数在 Excel 执行重新计算时每次都会被评估 —— 无论函数的依赖是否已更改。像 =RAND()
或 =NOW()
这样的几个内置函数是不稳定的,过多使用它们会使你的工作簿变慢,因此不要过度使用。当你修改函数的名称或参数或者像我们刚刚做的那样修改 func
装饰器时,你需要再次点击导入函数按钮重新导入函数:这将在导入更新的函数之前重新启动 Python 解释器。现在如果你将函数从 Bye
改回 Hello
,只需使用键盘快捷键 Shift+F9 或 F9 即可导致公式重新计算,因为此函数现在是不稳定的。
修改 Python 文件后保存
一个常见的坑是在修改 Python 源文件后忘记保存。因此,在点击导入函数按钮或在 Excel 中重新计算 UDF 之前,务必双检 Python 文件已保存。
默认情况下,xlwings 从与 Excel 文件同名且在同一目录中的 Python 文件中导入函数。重命名和移动你的 Python 源文件需要类似于 第十章 中我们对 RunPython
调用所做的更改:请将文件从 first_udf.py 改名为 hello.py。为了让 xlwings 知道这个变化,将模块的名称,即 hello
(不带 .py 扩展名!),添加到 xlwings 插件中的 UDF 模块中,如 图 12-3 所示。
图 12-3. UDF 模块设置
点击导入函数按钮重新导入函数。然后在 Excel 中重新计算公式以确保一切仍然正常工作。
从多个 Python 模块导入函数
如果你想从多个模块导入函数,在 UDF 模块设置中它们的名称之间需要用分号分隔,例如,
hello;another_module
。
现在,请继续将 hello.py 移动到你的桌面上:这需要你在 xlwings 插件中将你的桌面路径添加到 PYTHONPATH
中。正如在 第十章 中所见,你可以使用环境变量来实现这一点,即你可以在插件中设置 PYTHONPATH
设置为 %USERPROFILE%\Desktop。如果你仍然将 pyscripts 文件夹的路径保留在那里从 第十章 中,则要么覆盖它,要么保留它在那里,并用分号分隔这些路径。完成这些更改后,再次点击导入函数按钮,然后在 Excel 中重新计算函数以验证一切仍然正常运行。
配置与部署
在本章中,我总是提到在附加组件中更改设置;然而,关于配置和部署的所有内容都可以适用于本章。这意味着设置也可以在 xlwings.conf 表或与 Excel 文件相同目录中的配置文件中进行更改。而且,您可以使用独立模式中设置的工作簿,而不是使用 xlwings 附加组件。对于 UDF 来说,构建您自己的自定义附加组件也是有意义的——这使您可以在所有工作簿之间共享您的 UDF,而无需将它们导入到每个工作簿中。有关构建您自己的自定义附加组件的更多信息,请参阅xlwings 文档。
如果更改 UDF 的 Python 代码,xlwings 会在每次保存 Python 文件时自动获取更改。如前所述,如果更改函数名称、参数或装饰器中的任何内容,则只需要重新导入您的 UDF。然而,如果您的源文件从其他模块导入代码,并且您在这些模块中进行了更改,让 Excel 获取所有更改的最简单方法是单击“重新启动 UDF 服务器”。
到目前为止,您已经知道如何在 Python 中编写简单的 UDF,并在 Excel 中使用它。接下来的案例研究将为您介绍更实际的 UDF,这些 UDF 利用了 pandas 数据框架。
案例研究:Google Trends
在这个案例研究中,我们将使用来自 Google Trends 的数据,学习如何处理 pandas 数据框架和动态数组,这是微软在 2020 年正式推出的 Excel 中最令人兴奋的新功能之一。然后,我们创建一个直接连接到 Google Trends 的 UDF,以及一个使用数据框架的plot
方法的 UDF。为了结束本节,我们将看看 UDF 的调试工作原理。让我们从 Google Trends 的简短介绍开始吧!
Google Trends 介绍
Google Trends 是 Google 的一项服务,允许您分析 Google 搜索查询随时间和地区的流行度。 Figure 12-4 显示了在添加了几种流行的编程语言后的 Google Trends,选择了全球作为地区和 1/1/16 - 12/26/20 作为时间范围。每个搜索词都以编程语言上下文进行了选择,这在键入搜索词后的下拉菜单中显示。这确保我们忽略了 Python 这条蛇和 Java 这个岛。 Google 在所选时间范围和位置内索引数据,100 表示最大的搜索兴趣。在我们的样本中,这意味着在给定的时间范围和位置内,最高的搜索兴趣是在 2016 年 2 月的 Java。有关 Google Trends 的更多详细信息,请参阅他们官方的博客文章。
图 12-4。随时间变化的兴趣;数据源Google Trends
随机样本
Google Trends 的数据基于随机样本,这意味着即使您在相同位置、时间段和搜索词上查看 Figure 12-4,您可能看到的图片也可能略有不同。
我点击 Figure 12-4 中看到的下载按钮,从中获取了一个 CSV 文件,然后将数据复制到了一个 quickstart
项目的 Excel 工作簿中。在接下来的部分,我将向您展示在哪里找到此工作簿——我们将使用它在 Excel 中直接分析数据!
使用 DataFrames 和动态数组
读至此书中,您不应感到惊讶,pandas DataFrames 也是 UDF 的好伴侣。要了解 DataFrames 和 UDF 如何配合,并了解动态数组,请转到 companion 代码库中 udfs 目录下的 describe 文件夹,并在 Excel 中打开 describe.xlsm 和在 VS Code 中打开 describe.py。Excel 文件包含来自 Google Trends 的数据,在 Python 文件中,您会找到一个简单的起步函数,如 Example 12-2 所示。
Example 12-2. describe.py
import``xlwings``as``xw``import``pandas``as``pd``@xw.func``@xw.arg``(``"df"``,``pd``.``DataFrame``,``index``=``True``,``header``=``True``)``def``describe``(``df``):``return``df``.``describe``()
与 quickstart
项目中的 hello
函数相比,您会注意到第二个装饰器的存在:
@xw.arg``(``"df"``,``pd``.``DataFrame``,``index``=``True``,``header``=``True``)
arg
是 argument 的缩写,允许您在介绍 xlwings 语法时使用相同的转换器和选项,这与 Chapter 9 中 xlwings range
对象的 options
方法提供了相同的功能。严格来说,这是 arg
装饰器的语法:
@xw.arg``(``"argument_name"``,``convert``=``None``,``option1``=``value1``,``option2``=``value2``,``...``)
要帮助您回到 Chapter 9,脚本形式中 describe
函数的等效方式如下(假设 describe.xlsm 已在 Excel 中打开,并且该函数应用于范围 A3:F263):
import``xlwings``as``xw``import``pandas``as``pd``data_range``=``xw``.``Book``(``"describe.xlsm"``)``.``sheets``[``0``][``"A3:F263"``]``df``=``data_range``.``options``(``pd``.``DataFrame``,``index``=``True``,``header``=``True``)``.``value``df``.``describe``()
选项index
和header
不需要,因为它们使用了默认参数,但我包含它们是为了向你展示它们在 UDFs 中的应用方式。在 describe.xlsm 作为你的活动工作簿时,点击“导入函数”按钮,然后在一个空白单元格中,例如 H3 中,输入=describe(A3:F263)
。当你按下 Enter 键时,根据你的 Excel 版本不同,会发生什么也会有所不同——具体取决于你的 Excel 版本是否足够新来支持动态数组。如果支持,你将看到如图 12-5 所示的情况,即 H3:M11 单元格中describe
函数的输出周围有一条细蓝色边框。只有在数组内部,你才能看到这个蓝色边框,并且它非常微妙,如果在书的印刷版本中查看截屏可能会有困难。我们稍后会看到动态数组的行为,你也可以在侧边栏“动态数组”中了解更多。
图 12-5。带动态数组的describe
函数
然而,如果你使用的是不支持动态数组的 Excel 版本,看起来就好像什么也没有发生:默认情况下,公式只会返回 H3 单元格的左上角,即空白。要解决这个问题,使用微软现在称之为传统 CSE 数组。CSE 数组需要通过键入 Ctrl+Shift+Enter 来确认,而不是仅仅按 Enter 键——因此得名。让我们详细看看它们的工作原理:
-
确保 H3 是一个空单元格,通过选择它并按 Delete 键。
-
-
通过从单元格 H3 开始选择输出范围,然后选择所有通向 M11 的单元格。
-
-
选中范围 H3:M11,输入公式
=describe(A3:F263)
,然后按 Ctrl+Shift+Enter 确认。
现在你应该看到与图 12-5 几乎相同的图片,但有以下几点不同:
-
在范围 H3:M11 周围没有蓝色边框。
-
-
该公式显示大括号以标记为 CSE 数组:
{=describe(A3:F263)}
。 -
-
要删除动态数组,只需转到左上角单元格,然后按 Delete 键即可,而对于 CSE 数组,则始终需要先选择整个数组才能删除它。
现在让我们通过引入一个名为selection
的可选参数来稍微改进我们的函数,这将允许我们指定在输出中包含哪些列。如果你有很多列,但只想在describe
函数中包含一个子集,这将成为一个有用的功能。按以下方式更改函数:
@xw.func``@xw.arg``(``"df"``,``pd``.``DataFrame``)
def``describe``(``df``,``selection``=``None``):
如果``selection``不为``None``:``return``df``.``loc``[:,``selection``]``.``describe``()
否则``:``return``df``.``describe``()
我省略了
index
和header
参数,因为它们使用默认值,但随时可以保留它们。
添加参数
selection
并将其默认值设置为None
以使其可选。
如果提供了
selection
,则根据它过滤 DataFrame 的列。
一旦您更改了函数,请确保保存,并在 xlwings 加载项中点击导入函数按钮 —— 这是必需的,因为我们添加了一个新参数。将 Selection
写入单元格 A2,并将 TRUE
写入单元格 B2:F2。最后,根据是否使用动态数组,调整单元格 H3 中的公式:
对于动态数组
选择 H3,然后将公式更改为
=describe(A3:F263, B2:F2)
并按 Enter 键。
对于非动态数组
从单元格 H3 开始,选择 H3:M11,然后按 F2 以激活单元格 H3 的编辑模式,并将公式更改为
=describe(A3:F263, B2:F2)
。最后,按 Ctrl+Shift+Enter 完成。
要尝试增强函数,请将单元格 E2 中的 Java 的 TRUE
更改为 FALSE
,并查看结果:使用动态数组时,您将看到表格神奇地减少了一列。然而,对于传统的 CSE 数组,您将得到一个充满 #N/A
值的丑陋列,如 图 12-6 所示。
为了解决这个问题,xlwings 可以通过使用返回装饰器来调整传统的 CSE 数组。通过修改函数如下添加它:
@xw.func``@xw.arg``(``"df"``,``pd``.``DataFrame``)``@xw.ret``(``expand``=``"table"``)
def``describe``(``df``,``selection``=``None``):``if``selection``is``not``None``:``return``df``.``loc``[:,``selection``]``.``describe``()``else``:``return``df``.``describe``()
通过添加返回装饰器并设置
expand="table"
选项,xlwings 将调整 CSE 数组的大小以匹配返回的 DataFrame 的维度。
图 12-6. 动态数组(顶部)与 CSE 数组(底部)在排除列后的比较
添加返回装饰器后,在保存 Python 源文件后,切换到 Excel,并按下 Ctrl+Alt+F9 进行重新计算:这将调整 CSE 数组的大小并移除 #N/A
列。由于这是一种变通方法,强烈建议您尽一切可能获取支持动态数组的 Excel 版本。
函数装饰器的顺序
确保将
xw.func
装饰器放在xw.arg
和xw.ret
装饰器之上;注意xw.arg
和xw.ret
的顺序无关紧要。
返回装饰器在概念上与参数装饰器相同,唯一的区别在于您无需指定参数的名称。其形式上的语法如下:
@xw.ret``(``convert``=``None``,``option1``=``value1``,``option2``=``value2``,``...``)
通常情况下,不需要提供显式的 convert
参数,因为 xlwings 会自动识别返回值的类型,这与我们在 第九章 中在写入 Excel 值时使用 options
方法时看到的行为相同。
例如,如果您想要抑制返回的 DataFrame 的索引,请使用此装饰器:
@xw.ret``(``index``=``False``)
动态数组
在了解了
describe
函数中动态数组的工作原理之后,我确信您会同意它们是 Microsoft Excel 中微软最近引入的最基础和最令人兴奋的功能之一。它们在 2020 年正式向使用最新版本 Excel 的 Microsoft 365 订阅用户介绍。要查看您的版本是否足够新,请检查是否存在新的UNIQUE
函数:在单元格中开始键入=UNIQUE
,如果 Excel 建议函数名称,则支持动态数组。如果您使用的是永久许可证的 Excel 而不是 Microsoft 365 订阅的一部分,则很可能在 2021 年发布的版本中获得支持,并且该版本可能称为 Office 2021. 这里有关动态数组行为的一些技术注释:
-
如果动态数组用值覆盖单元格,则会出现
#SPILL!
错误。通过删除或移动挡住动态数组的单元格来为动态数组腾出空间后,数组将被写出。请注意,带有expand="table"
的 xlwings 返回装饰器较不智能,并且会在没有警告的情况下覆盖现有单元格值! -
-
您可以通过使用左上角单元格后跟
#
号来引用动态数组的范围。例如,如果您的动态数组位于 A1:B2 范围内,并且您希望对所有单元格求和,写入=SUM(A1#)
。 -
-
如果您希望您的数组再次像传统的 CSE 数组一样工作,请在公式前面加上
@
符号,例如,要使矩阵乘法返回传统的 CSE 数组,请使用=@MMULT()
。
下载 CSV 文件并将值复制/粘贴到 Excel 文件中对于这个介绍性的 DataFrame 示例效果很好,但是复制/粘贴是一个容易出错的过程,你会想尽可能地摆脱它。使用 Google 趋势,您确实可以这样做,接下来的部分将向您展示如何!
从谷歌趋势获取数据
前面的例子都非常简单,基本上只是包装了一个 pandas 函数。为了获取更真实的案例,让我们创建一个 UDF,直接从 Google Trends 下载数据,这样您就不必在线手动下载 CSV 文件了。Google Trends 没有官方的 API(应用程序编程接口),但有一个名为 pytrends 的 Python 包填补了这个空白。并非官方 API 意味着 Google 随时可能更改它,所以这一部分的示例有停止工作的风险。然而,考虑到 pytrends 在撰写本文时已经存在了五年多,也有可能会更新以反映这些变化并使其再次可用。无论如何,它都是一个很好的例子,向您展示几乎任何事情都有 Python 包——这是我在 第一章 中提到的。如果您被限制使用 Power Query,您可能需要投入更多时间才能使其正常工作——至少我找不到一个免费提供的即插即用解决方案。由于 pytrends 不是 Anaconda 的一部分,也没有官方的 Conda 包,所以让我们用 pip 安装它,如果您还没有这样做的话:
(base)>
pip install pytrends
要复制在线版谷歌趋势中显示的确切情况,如 图 12-4 所示,我们需要找到带有“编程语言”上下文的正确标识符。为此,pytrends 可以打印出谷歌趋势在下拉菜单中建议的不同搜索上下文或类型。在以下代码示例中,mid
代表机器 ID,这就是我们要找的 ID:
In``[``4``]:``从``pytrends.request``导入``TrendReq
In``[``5``]:``# 首先,让我们实例化一个 TrendRequest 对象``trend``=``TrendReq``()
In``[``6``]:``# 现在我们可以打印出谷歌趋势搜索框中键入
"Python"后出现的建议
Out[6]: [{'mid': '/m/05z1_', 'title': 'Python', 'type': 'Programming language'}, {'mid': '/m/05tb5', 'title': 'Python family', 'type': 'Snake'}, {'mid': '/m/0cv6_m', 'title': 'Pythons', 'type': 'Snake'}, {'mid': '/m/06bxxb', 'title': 'CPython', 'type': 'Topic'}, {'mid': '/g/1q6j3gsvm', 'title': 'python', 'type': 'Topic'}]
对其他编程语言重复此操作允许我们检索出所有正确的 mid
,并且我们可以按照 Example 12-3 中显示的方式编写 UDF。您可以在同伴存储库的 google_trends 文件夹中的 udfs 文件夹中找到源代码。
Example 12-3. 在 google_trends.py 中的 get_interest_over_time
函数(包含相关的导入语句片段)
import``pandas``as``pd``from``pytrends.request``import``TrendReq``import``xlwings``as``xw``@xw.func``(``call_in_wizard``=``False``)
@xw.arg``(``"mids"``,``doc``=``"机器 ID:最多 5 个单元格的范围"``)
@xw.arg``(``"start_date"``,``doc``=``"日期格式的单元格"``)``@xw.arg``(``"end_date"``,``doc``=``"日期格式的单元格"``)``def``get_interest_over_time``(``mids``,``start_date``,``end_date``):``"""查询 Google Trends - 替换返回值中的常见编程语言的机器 ID(mid)为其人类可读等效形式,例如,代替"/m/05z1_",返回"Python"。"""
# 检查和转换参数``assert``len``(``mids``)``<=``5``,``"mids 过多(最多:5 个)"``
start_date=
start_date.
date()
.isoformat
()`end_date
=end_date
.date
().
isoformat()
# 进行 Google Trends 请求并返回 DataFrametrend
=TrendReq
(timeout
=10
)
trend.
build_payload(
kw_list=
mids,
timeframe=
f"{start_date} {end_date}"
)
df=
trend.
interest_over_time()``# 用人类可读词替换 Google 的"mid"
mids=
{"/m/05z1_"
:"Python"
,"/m/02p97"
:"JavaScript"
,"/m/0jgqg"
:"C++"
,"/m/07sbkfb"
:"Java"
,"/m/060kv"
:"PHP"
}df
=df
.rename
(columns
=mids
)
# 删除 isPartial 列return
df.
drop(
columns=
"isPartial"``)`
默认情况下,当您在函数向导中打开 Excel 时,它会调用该函数。由于这可能会使其变慢,特别是涉及 API 请求时,我们将其关闭。
可选地,为函数参数添加文档字符串,在您编辑相应参数时将显示在函数向导中,如图 12-8。
函数的文档字符串显示在函数向导中,如图 12-8。
assert
语句是在用户提供了太多mids
时引发错误的简便方式。Google Trends 每次查询最多允许五个mids
。
pytrends 期望的开始和结束日期是一个形如
YYYY-MM-DD YYYY-MM-DD
的字符串。因为我们提供的是日期格式的单元格作为开始和结束日期,它们将作为datetime
对象到达。在它们上调用date
和isoformat
方法将正确地格式化它们。
我们正在实例化一个 pytrends 的
request
对象。通过将timeout
设置为十秒,我们减少了看到requests.exceptions.ReadTimeout
错误的风险,这种错误偶尔会发生,如果 Google Trends 需要更长时间来响应的话。如果仍然看到此错误,只需再次运行函数或增加超时时间。
我们为请求对象提供
kw_list
和timeframe
参数。
通过调用
interest_over_time
来发出实际请求,它将返回一个 pandas DataFrame。
我们使用其可读的人类等价物重新命名
mids
。
最后一列称为
isPartial
。True
表示当前间隔(例如一周)仍在进行中,因此尚未完全具备所有数据。为了保持简单并与在线版本保持一致,我们在返回 DataFrame 时删除此列。
现在从伴随存储库中打开google_trends.xlsm
,在xlwings
加载项中单击“导入函数”,然后从单元格 A4 中调用get_interest_over_time
函数,如图 12-7 所示。
图 12-7. google_trends.xlsm
若要获取有关函数参数的帮助,请在选择单元格 A4 时,单击公式栏左侧的“插入函数”按钮:这将打开函数向导,在其中您将在xlwings
类别下找到您的 UDFs。选择get_interest_over_time
后,您将看到函数参数的名称以及作为函数描述的文档字符串(限制为前 256 个字符):参见图 12-8。或者,在单元格 A4 中开始输入=get_interest_over_time(
(包括开放括号),然后再单击“插入函数”按钮,这将直接带您进入图 12-8 所示的视图。请注意,UDFs 返回未格式化的日期。要修复此问题,请右键单击日期列,选择“格式单元格”,然后在“日期”类别下选择您喜欢的格式。
图 12-8. 函数向导
如果您仔细观察图 12-7,您可以通过结果数组周围的蓝色边框判断我再次使用了动态数组。由于截图在底部被裁剪,数组从左侧开始,因此您仅能看到从单元格 A4 开始的顶部和右侧边界,即使它们可能在截图上也很难识别。如果您的 Excel 版本不支持动态数组,请通过在get_interest_over_time
函数中添加以下返回装饰器来使用以下解决方法(在现有装饰器下方):
@xw.ret``(``expand``=``"table"``)
现在您已经了解如何处理更复杂的 UDFs,让我们看看如何在 UDFs 中使用图表!
使用用户定义函数绘图
正如您可能还记得来自第五章,调用 DataFrame 的plot
方法默认返回一个 Matplotlib 图。在第九章和第十一章中,我们已经看到如何将这样的图作为图片添加到 Excel 中。当使用 UDFs 时,生成图表有一种简便的方法:看一下google_trends.py
中的第二个函数,如示例 12-4 所示。
示例 12-4. google_trends.py 中的plot
函数(节选与相关导入语句)
import``xlwings``as``xw``import``pandas``as``pd``import``matplotlib.pyplot``as``plt``@xw.func``@xw.arg``(``"df"``,``pd``.``DataFrame``)``def``plot``(``df``,``name``,``caller``):
plt``.``style``.``use``(``"seaborn"``)
if``not``df``.``empty``:
caller``.``sheet``.``pictures``.``add``(``df``.``plot``()``.``get_figure``(),
top``=``caller``.``offset``(``row_offset``=``1``)``.``top``,
left``=``caller``.``left``,``name``=``name``,``update``=``True``)
return``f``"<Plot: {name}>"
caller
参数是 xlwings 专用的参数:在从 Excel 单元格调用函数时,此参数不会暴露给用户。相反,xlwings 会在后台提供caller
,对应于调用函数的单元格(以 xlwingsrange
对象的形式)。通过使用pictures.add
的top
和left
参数,可以轻松地放置图形。name
参数将定义 Excel 中图片的名称。
我们设置了
seaborn
风格,使绘图在视觉上更具吸引力。
只有在 DataFrame 不为空时才调用
plot
方法。在空 DataFrame 上调用plot
方法会引发错误。
get_figure()
从 DataFrame 绘图返回 Matplotlib 图形对象,这是pictures.add
所期望的。
参数
top
和left
仅在首次插入图时使用。提供的参数将在方便的位置放置图——即从调用此函数的单元格下一行。
参数
update=True
确保重复调用函数时,将更新 Excel 中具有提供名称的现有图片,而不会更改其位置或大小。没有这个参数,xlwings 会抱怨 Excel 中已经有同名图片。
虽然您不一定需要返回任何内容,但如果返回一个字符串,将大大简化您的生活:这样可以让您识别出表格中绘图函数的位置。
在 google_trends.xlsm 中,如在单元格 H3 中调用plot
函数:
=``plot``(``A4``:``F263``,``"History"``)
如果您的 Excel 版本支持动态数组,请使用A4#
而不是A4:F263
,使源动态化,如图 12-9 所示。
图 12-9. plot
函数的示例
假设你对get_interest_over_time
函数的工作方式有些困惑。要更好地理解它的一种选择是调试代码——下一节将展示如何使用 UDFs!
调试 UDFs
调试 UDF 的简单方法是使用 print
函数。如果您在 xlwings 插件中启用了 Show Console 设置,则在调用 UDF 时,可以在显示的命令提示符中打印变量的值。稍微更舒适的选项是使用 VS Code 的调试器,它允许您在断点处暂停并逐行执行代码。要使用 VS Code 调试器(或任何其他 IDE 的调试器),您需要完成两件事:
-
在 Excel 插件中,激活复选框 Debug UDFs。这样可以防止 Excel 自动启动 Python,这意味着您需要按照下一个步骤手动进行操作。
-
-
手动运行 Python UDF 服务器的最简单方法是在您要调试的文件的最底部添加以下几行。我已经在配套仓库的 google_trends.py 文件的底部添加了这些行:
-
if
__name__
==``"__main__"``:``xw``.``serve``()
您可能还记得来自 第 11 章 的这个
if
语句,它确保代码仅在作为脚本运行文件时运行—当您将代码作为模块导入时不会运行。添加了serve
命令后,通过按 F5 并选择“Python File”在 VS Code 中以调试模式运行 google_trends.py—请确保不要通过单击“Run File”按钮来运行文件,因为这将忽略断点。
通过在行号左侧单击设置断点到第 29 行。如果您不熟悉使用 VS Code 的调试器,请参阅 附录 B,我在那里详细介绍了它。现在重新计算单元格 A4,您的函数调用将在断点处停止,您可以检查变量。在调试过程中始终有帮助的是运行 df.info()
。激活 Debug Console 标签,将 df.info()
写入底部提示符中,并通过按 Enter 键确认,如 Figure 12-10 所示。
使用 VS Code 和 ANACONDA 进行调试
这与 第 11 章 中的相同警告相同:在 Windows 上,当您首次使用使用 pandas 的代码运行 VS Code 调试器时,您可能会遇到错误:“Exception has occurred: ImportError, Unable to import required dependencies: numpy。”这是因为调试器在 Conda 环境正确激活之前已经运行。作为解决方法,请单击停止图标停止调试器,然后再次按 F5—第二次将起作用。
图 12-10. 在代码暂停在断点时使用 Debug Console
如果您在断点上暂停程序超过 90 秒,Excel 将显示一个弹出窗口,提示“Microsoft Excel 正在等待另一个应用完成 OLE 操作”。这不会对您的调试体验产生影响,除非您完成调试后需要确认弹出窗口才能使其消失。要结束此调试会话,请单击 VS Code 中的停止按钮(参见图 12-10),并确保再次取消选中 xlwings 功能区插件中的 Debug UDFs 设置。如果忘记取消选中 Debug UDFs 设置,则在下次重新计算时,您的函数将返回错误。
本节通过进行 Google 趋势案例研究,向您展示了最常用的 UDF 功能。接下来的部分将涉及一些高级主题,包括 UDF 性能和xw.sub
装饰器。
高级 UDF 主题
如果您的工作簿中使用了许多 UDF,性能可能成为一个问题。本节将从基本性能优化入手,如同我们在第九章中所见,但这次应用于 UDF。第二部分涉及缓存,这是我们可以在 UDF 中使用的额外性能优化技术。在学习的过程中,我们还将了解如何将函数参数作为 xlwings range
对象而不是值传递。在本节的最后,我将向您介绍xw.sub
装饰器,您可以在仅在 Windows 上工作时,作为RunPython
调用的替代方案使用。
基本性能优化
本部分将讨论两种性能优化技术:如何最小化跨应用程序调用以及如何使用原始值转换器。
减少跨应用程序调用
正如您可能从第九章中回忆起的那样,跨应用程序调用即在 Excel 和 Python 之间进行的调用通常较慢,因此您拥有的 UDF 越少,效果越好。因此,您应尽可能使用数组工作——拥有支持动态数组的 Excel 版本肯定会使此部分更加容易。当您使用 pandas DataFrames 时,几乎不会出现问题,但在某些公式中,您可能没有自动考虑使用数组。请考虑图 12-11 的示例,该示例将总收入计算为给定基础费用加上由用户乘以价格确定的变量费用。
图 12-11. 单元格公式(左)与基于数组的公式(右)
单元格公式
图 12-11 中的左侧表格使用公式
=revenue($B$5, $A9, B$8)
在单元格 B9 中。然后将此公式应用于整个范围 B9:E13。这意味着您有 20 个单元格公式调用revenue
函数。
基于数组的公式
图 12-11 中的右表使用公式
=revenue2(H5, G9:G13, H8:K8)
。如果您的 Excel 版本没有动态数组,您需要向revenue2
函数添加装饰器xw.ret(expand="table")
或者通过选择 H9:K13,按 F2 编辑公式,并使用 Ctrl+Shift+Enter 确认将数组转换为传统的 CSE 数组。与单元格公式不同,此版本仅调用一次revenue2
函数。
您可以在示例 12-5 中查看两个 UDF 的 Python 代码,并且您将在伴随存储库的 udfs 目录中的 revenues 文件夹中找到源文件。
示例 12-5. revenues.py
import``numpy``as``np``import``xlwings``as``xw``@xw.func``def``revenue``(``base_fee``,``users``,``price``):``return``base_fee``+``users``*``price``@xw.func``@xw.arg``(``"users"``,``np``.``array``,``ndim``=``2``)``@xw.arg``(``"price"``,``np``.``array``)``def``revenue2``(``base_fee``,``users``,``price``):``return``base_fee``+``users``*``price
当您在单元格 B5 或 H5 中更改基本费用时,您将看到右侧示例比左侧示例快得多。Python 函数中的差异很小,只在参数装饰器中有所不同:基于数组的版本将users
和prices
作为 NumPy 数组读取进来——这里唯一的注意事项是通过在参数装饰器中设置ndim=2
将users
读入为二维列向量。您可能还记得,NumPy 数组类似于 DataFrames,但没有索引或标题,并且只有一种数据类型,但如果您需要更详细的复习,请再看一下第 4 章。
使用原始值
使用原始值意味着您略去了 xlwings 在 pywin32 之上执行的数据准备和清理步骤,xlwings 对 Windows 的依赖。例如,这意味着您不能再直接使用 DataFrames,因为 pywin32 不理解它们,但如果您使用列表或 NumPy 数组,则可能不是问题。要使用带有原始值的 UDFs,请在参数或返回装饰器中使用字符串raw
作为convert
参数。这相当于通过 xlwings range
对象的options
方法使用raw
转换器,正如我们在第 9 章中所做的那样。与我们当时看到的情况一致,您将在写入操作期间获得最大的速度提升。例如,如果不使用返回装饰器调用以下函数,它在我的笔记本电脑上会慢大约三倍:
import``numpy``as``np``import``xlwings``as``xw``@xw.func``@xw.ret``(``"raw"``)``def``randn``(``i``=``1000``,``j``=``1000``):``"""返回一个维度为(i,j)的数组,其中包含由 NumPy 的 random.randn 提供的正态分布伪随机数"""``return``np``.``random``.``randn``(``i``,``j``)
你将在伴随的存储库中的udfs
目录下的raw_values
文件夹中找到相应的示例。在使用 UDF 时,你有另一个简单的选项来提高性能:通过缓存其结果来防止重复计算缓慢函数。
缓存
当你调用一个确定性函数时,即给定相同输入时总是返回相同输出的函数,你可以将结果存储在缓存中:函数的重复调用不再需要等待缓慢的计算,而是可以从缓存中获取已经预先计算好的结果。这最好通过一个简短的例子来解释。一个非常基本的缓存机制可以用字典编程:
In [7]: import time
In [8]: cache = {} def slow_sum(a, b): key = (a, b) if key in cache: return cache[key] else: time.sleep(2) # sleep for 2 seconds result = a + b cache[key] = result return result
当你第一次调用这个函数时,cache
是空的。因此代码将执行模拟缓慢计算的人工两秒暂停的else
子句。执行计算后,它将结果添加到cache
字典中,然后返回结果。当你在同一个 Python 会话中第二次调用具有相同参数的这个函数时,它将在cache
中找到并立即返回它,而不需要再次执行缓慢的计算。根据其参数缓存结果也称为记忆化。因此,当你第一次和第二次调用函数时,你将看到时间差异:
In [9]: %%time slow_sum(1, 2)
Wall time: 2.01 s
Out[9]: 3
In [10]: %%time slow_sum(1, 2)
Wall time: 0 ns
Out[10]: 3
Python 中有一个内置的装饰器称为lru_cache
,可以极大地简化你的生活,并且你可以从标准库的functools
模块中导入它。lru
代表最近最少使用缓存,意味着它在丢弃最旧的条目之前可以保存最大数量的结果(默认为 128)。我们可以在上一节的 Google 趋势示例中使用它。只要我们只查询历史值,我们就可以安全地缓存结果。这不仅会加快多次调用的速度,还会减少发送给 Google 的请求量,从而降低在短时间内发送过多请求而被 Google 封锁的风险。
下面是get_interest_over_time
函数的前几行,应用缓存所需的更改:
from``functools``import``lru_cache
import``pandas``as``pd``from``pytrends.request``import``TrendReq``import``matplotlib.pyplot``as``plt``import``xlwings``as``xw``@lru_cache
@xw.func``(``call_in_wizard``=``False``)``@xw.arg``(``"mids"``,``xw``.``Range``,``doc``=``"Machine IDs: A range of max 5 cells"``)
@xw.arg``(``"start_date"``,``doc``=``"A date-formatted cell"``)``@xw.arg``(``"end_date"``,``doc``=``"A date-formatted cell"``)``def``get_interest_over_time``(``mids``,``start_date``,``end_date``):``"""Query Google Trends - replaces the Machine ID (mid) of common programming languages with their human-readable equivalent in the return value, e.g., instead of "/m/05z1_" it returns "Python". """``mids``=``mids``.``value
导入
lru_cache
装饰器。
使用装饰器。装饰器必须位于
xw.func
装饰器的顶部。
默认情况下,
mids
是一个列表。在这种情况下会出现问题,因为将列表作为参数的函数无法被缓存。根本问题在于列表是可变对象,不能作为字典中的键使用;有关可变与不可变对象的更多信息,请参见附录 C。使用xw.Range
转换器允许我们将mids
作为 xlwings 的range
对象检索,而不是作为列表,从而解决了我们的问题。
为了使其余代码再次正常工作,现在我们需要通过 xlwings 的
range
对象的value
属性获取值。使用不同版本的 Python 进行缓存
如果您使用的是低于 3.8 版本的 Python,则必须像这样使用带括号的装饰器:
@lru_cache()
。如果您使用的是 Python 3.9 或更高版本,请用@cache
替换@lru_cache
,这等同于@lru_cache(maxsize=None)
,即缓存永不删除旧值。您还需要从functools
导入cache
装饰器。
xw.Range
转换器在其他情况下也可能很有用,例如,如果您需要访问单元格公式而不是 UDF 中的值。在前面的示例中,您可以编写mids.formula
来访问单元格的公式。您可以在同伴存储库的 udfs 目录中的 google_trends_cache 文件夹中找到完整的示例。
现在您已经了解如何调整 UDF 的性能,让我们通过介绍xw.sub
装饰器来结束本节。
子装饰器
在 第 10 章 中,我向您展示了如何通过激活“使用 UDF 服务器”设置来加速 RunPython
调用。如果您生活在仅限 Windows 的世界中,RunPython
/使用 UDF 服务器
组合的替代方案是 xw.sub
装饰器。这将允许您将 Python 函数作为 Sub 程序导入到 Excel 中,而无需手动编写任何 RunPython
调用。在 Excel 中,您需要一个 Sub 程序才能将其附加到按钮上 —— 使用 xw.func
装饰器时获得的 Excel 函数将不起作用。要尝试这个功能,请创建一个名为 importsub
的新的 quickstart
项目。像往常一样,请确保首先 cd
到要创建项目的目录中:
(base)>
xlwings quickstart importsub
在文件资源管理器中,导航到创建的 importsub 文件夹,并在 Excel 中打开 importsub.xlsm,在 VS Code 中打开 importsub.py,然后按照 示例 12-6 中所示,用 @xw.sub
装饰 main
函数。
示例 12-6. importsub.py(摘录)
import``xlwings``as``xw``@xw.sub``def``main``():``wb``=``xw``.``Book``.``caller``()``sheet``=``wb``.``sheets``[``0``]``if``sheet``[``"A1"``]``.``value``==``"Hello xlwings!"``:``sheet``[``"A1"``]``.``value``=``"Bye xlwings!"``else``:``sheet``[``"A1"``]``.``value``=``"Hello xlwings!"
在 xlwings 加载项中,单击导入函数,然后按 Alt+F8 查看可用的宏:除了使用 RunPython
的 SampleCall
外,现在还会看到一个名为 main
的宏。选择它,然后单击“运行”按钮 —— 您将在单元格 A1 中看到熟悉的问候语。现在,您可以继续将 main
宏分配给按钮,就像我们在 第 10 章 中所做的那样。虽然 xw.sub
装饰器可以在 Windows 上简化您的生活,但请记住,使用它会丧失跨平台兼容性。通过 xw.sub
,我们已经介绍了所有 xlwings 装饰器 —— 我再次在 表 12-1 中对它们进行了总结。
表 12-1. xlwings 装饰器
装饰器 | 描述 |
---|---|
xw.func |
将此装饰器放在您希望作为 Excel 函数导入的所有函数的顶部。 |
xw.sub |
将此装饰器放在您希望作为 Excel Sub 程序导入的所有函数的顶部。 |
xw.arg |
应用转换器和选项到参数,例如,通过 doc 参数添加文档字符串,或者通过将 pd.DataFrame 作为第一个参数来将范围作为 DataFrame 传入(这假设您已将 pandas 导入为 pd)。 |
xw.ret |
应用转换器和选项以返回值,例如,通过提供 index=False 来抑制 DataFrame 的索引。 |
欲了解更多有关这些装饰器的详细信息,请参阅 xlwings 文档。
结论
本章是关于编写 Python 函数并将其导入 Excel 作为 UDFs,使您能够通过单元格公式调用它们。通过通过 Google Trends 案例研究来影响函数参数和返回值的行为,您学会了如何使用arg
和ret
装饰器分别来影响函数参数和返回值的行为。最后一部分向您展示了一些性能技巧,并介绍了xw.sub
装饰器,如果您仅在 Windows 上工作,您可以将其用作RunPython
的替代方案。使用 Python 编写 UDFs 的好处之一是,这使您可以将长而复杂的单元格公式替换为更容易理解和维护的 Python 代码。我的首选 UDFs 工作方式肯定是使用 pandas DataFrames 与 Excel 的新动态数组,这种组合使得处理来自 Google Trends 的数据(即具有动态行数的 DataFrames)变得容易。
就是这样了——我们已经到达了书的结尾!非常感谢您对我对于现代化的 Excel 自动化和数据分析环境的诠释感兴趣!我的想法是向您介绍 Python 世界及其强大的开源包,使您能够为下一个项目编写 Python 代码,而不必处理 Excel 的自身解决方案,如 VBA 或 Power Query,从而保持一扇通向轻松摆脱 Excel 的大门。我希望我能为您提供一些实用例子,以使您的起步更加容易。阅读完本书后,您现在知道如何:
-
将 Excel 工作簿替换为 Jupyter 笔记本和 pandas 代码
-
-
使用 OpenPyXL、xlrd、pyxlsb 或 xlwings 读取 Excel 工作簿,然后通过 pandas 合并它们,批处理 Excel 工作簿
-
-
使用 OpenPyXL、XlsxWriter、xlwt 或 xlwings 生成 Excel 报告
-
-
使用 Excel 作为前端,并通过 xlwings 将其连接到几乎任何您想要的内容,无论是通过点击按钮还是编写 UDF
不久之后,你将会想要超越本书的范围。我邀请你不时查看书籍主页以获取更新和额外材料。本着这种精神,这里有一些你可以自行探索的想法:
-
使用 Windows 任务计划程序或 macOS 或 Linux 上的 cron 作业定期运行 Python 脚本。例如,您可以每个星期五基于从 REST API 或数据库获取的数据创建一个 Excel 报告。
-
-
编写一个 Python 脚本,当您 Excel 文件中的值满足某个条件时发送电子邮件提醒。也许是当您从多个工作簿合并的账户余额低于某个值时,或者当它显示与您内部数据库预期值不符的值时。
-
-
编写可以找出 Excel 工作簿中错误的代码:检查诸如
#REF!
或#VALUE!
的单元格错误,或逻辑错误,例如确保公式包括了所有应包括的单元格。如果你开始用专业的版本控制系统如 Git 跟踪你的关键工作簿,甚至可以在每次提交新版本时自动运行这些测试。
如果这本书激励你自动化每日或每周下载数据并粘贴到 Excel 中的例行工作,我会感到非常高兴。自动化不仅可以节省你的时间,还能大大减少出错的几率。如果你有任何反馈,请告诉我!你可以通过 O’Reilly、在 伴侣仓库 上开一个问题,或在 Twitter 上联系我 @felixzumstein。
1 Windows 实现使用了一个 COM 服务器(我在 第九章 中简要介绍了 COM 技术)。由于 macOS 上没有 COM,UDF 需要从头重新实现,这是一项繁重的工作,目前还没有完成。
附录 A. Conda 环境
在 第二章 中,我通过解释 Anaconda Prompt 中以 (base)
开头表示当前活动的 Conda 环境名称为 base
来介绍了 Conda 环境。Anaconda 要求你始终在激活的环境中工作,但当你在 Windows 上启动 Anaconda Prompt 或 macOS 上的 Terminal 时,base
环境会自动激活。使用 Conda 环境可以很好地分离项目的依赖关系:如果你想尝试一个新版本的包,比如 pandas,而不改变 base
环境,你可以在一个单独的 Conda 环境中这样做。在附录的第一部分中,我将向你展示如何创建一个名为 xl38
的 Conda 环境,我们将在其中安装我写这本书时使用的所有包的版本。这将使你能够按原样运行本书中的示例,即使其中某些包已发布了具有破坏性更改的新版本。在第二部分中,我将向你展示如何禁用 base
环境的自动激活,如果你不喜欢默认行为的话。
创建一个新的 Conda 环境
在 Anaconda Prompt 上运行以下命令来创建一个名为 xl38
的新环境,使用 Python 3.8:
(base)>
conda create --name xl38 python=3.8
按下 Enter 键时,Conda 将打印将要安装到新环境中的内容,并要求你确认:
继续 ([y]/n)?
按下 Enter 键确认,或输入 n
取消。安装完成后,像这样激活你的新环境:
(base)>
conda activate xl38
(xl38)>
环境名称已从base
更改为xl38
,现在您可以使用 Conda 或 pip 将软件包安装到这个新环境中,而不会影响任何其他环境(作为提醒:仅在 Conda 中找不到软件包时使用 pip)。让我们继续安装本书中使用的所有软件包版本。首先,请确保您处于xl38
环境中,即 Anaconda Prompt 显示(xl38)
,然后像这样安装 Conda 软件包(以下命令应作为单个命令输入;换行仅用于显示目的):
(xl38)>
conda install lxml=4.6.1 matplotlib=3.3.2 notebook=6.1.4 openpyxl=3.0.5 pandas=1.1.3 pillow=8.0.1 plotly=4.14.1 flake8=3.8.4 python-dateutil=2.8.1 requests=2.24.0 sqlalchemy=1.3.20 xlrd=1.2.0 xlsxwriter=1.3.7 xlutils=2.0.0 xlwings=0.20.8 xlwt=1.3.0
确认安装计划并通过 pip 安装剩余的两个依赖项来完成环境:
(xl38)>
pip install pyxlsb==1.0.7 pytrends==4.7.3
(xl38)>
如何使用 XL38 环境
如果您想要在本书中使用
xl38
环境而不是base
环境来处理示例,请确保始终通过运行以下命令激活您的xl38
环境:
(base)>
conda activate xl38
换句话说,无论我在 Anaconda 提示符中显示
(base)>
,您都需要显示(xl38)>
。
要再次取消激活环境并返回base
环境,请输入:
(xl38)>
conda deactivate
(base)>
如果您想完全删除环境,请运行以下命令:
(base)>
conda env remove --name xl38
您还可以利用配套存储库的 conda 文件夹中包含的环境文件 xl38.yml,而不是手动执行创建xl38
环境的步骤。运行以下命令将处理所有事务:
(base)>
cd C:\Users\``username``\python-for-excel\conda
(base)>
conda env create -f xl38.yml
(base)>
conda activate xl38
(xl38)>
默认情况下,每当您在 macOS 上打开终端或在 Windows 上打开 Anaconda 提示符时,Anaconda 总是激活base
环境。如果您不喜欢这样做,您可以按照我将向您展示的步骤禁用自动激活。
禁用自动激活
如果您不希望每次启动 Anaconda 提示符时自动激活base
环境,您可以禁用它:这将要求您在使用 Python 之前在命令提示符(Windows)或终端(macOS)上手动输入conda activate base
。
Windows
在 Windows 上,您需要在普通命令提示符中使用,而不是 Anaconda 提示符。以下步骤将在正常的命令提示符中启用
conda
命令。确保在第一行中替换 Anaconda 在您系统上安装的路径:
>
cd C:\Users\``username``\Anaconda3\condabin
>
conda init cmd.exe
现在您的常规命令提示符已经设置为 Conda,因此以后您可以像这样激活
base
环境:
>
conda activate base
(base)>
macOS
在 macOS 上,只需在终端中运行以下命令即可禁用自动激活:
(base)>
conda config --set auto_activate_base false
如果您希望恢复,再次运行相同命令并使用
true
替换false
。更改将在重新启动终端后生效。未来,您需要在使用python
命令之前激活base
环境:
>
conda activate base
(base)>
附录 B. 高级 VS Code 功能
本附录向您展示了在 VS Code 中调试器的工作原理以及如何直接从 VS Code 中运行 Jupyter 笔记本。这些主题是彼此独立的,因此您可以按任意顺序阅读它们。
调试器
如果您曾经在 Excel 中使用 VBA 调试器,我有个好消息:使用 VS Code 进行调试非常类似。让我们从在 VS Code 中打开伴随仓库中的 debugging.py 文件开始。点击第 4 行行号左侧的边距,以便出现一个红点—这是代码执行将暂停的断点。现在按 F5 开始调试:命令面板将显示一组调试配置选项。选择“Python 文件”以调试活动文件并运行代码直到达到断点。该行将被突出显示,并且代码执行暂停,请参见 图 B-1。在调试过程中,状态栏将变为橙色。
如果“变量”部分未自动显示在左侧,请确保单击“运行”菜单以查看变量的值。或者,您还可以将鼠标悬停在源代码中的变量上,并获取带有其值的工具提示。在顶部,您将看到调试工具栏,依次提供以下按钮:继续、跳过、步入、步出、重启和停止。当您将鼠标悬停在它们上方时,还将看到键盘快捷键。
图 B-1. VS Code 调试器在断点处停止
让我们看看每个按钮的功能:
继续
这将继续运行程序,直到它达到下一个断点或程序结束。如果它到达程序的末尾,调试过程将停止。
跳过
调试器将前进一行。"跳过" 意味着调试器不会在当前范围内的代码行上逐行步进。例如,它不会逐行步进调用的函数的代码—但函数仍将被调用!
步入
如果您的代码调用了函数或类等,"步入" 将导致调试器进入该函数或类。如果函数或类位于不同文件中,则调试器将为您打开此文件。
步出
如果使用“步进入”进入了函数,则“步进出”会导致调试器返回到下一个更高级别,直到最终返回到最初调用“步进入”的最高级别。
重新启动
这将停止当前的调试过程并从头开始新的调试。
停止
这将停止当前的调试过程。
现在您知道每个按钮的作用,请点击“步进”以推进一行并查看变量 c
如何出现在“变量”部分中,然后通过点击“继续”来完成此调试练习。
如果您保存了调试配置,命令面板将不会弹出并在每次按 F5 时询问您配置:点击活动栏中的运行图标,然后点击“创建一个 launch.json 文件”。这将导致命令面板再次显示,并且当您选择“Python 文件”时,它会在名为 .vscode 的目录下创建 launch.json 文件。现在再次按 F5,调试器将立即启动。如果您需要更改配置或想再次弹出命令面板,请编辑或删除 .vscode 目录中的 launch.json 文件。
VS Code 中的 Jupyter 笔记本
您也可以直接在 VS Code 中运行 Jupyter 笔记本,而不是在 Web 浏览器中运行它们。除此之外,VS Code 还提供了便捷的变量资源管理器,并提供将笔记本转换为标准 Python 文件的选项,而不会丢失单元格功能。这使得使用调试器或在不同笔记本之间复制/粘贴单元格变得更加容易。让我们通过在 VS Code 中运行笔记本来开始吧!
运行 Jupyter 笔记本
点击活动栏上的资源管理器图标,从伴随存储库中打开 ch05.ipynb。要继续,请点击弹出的通知中的“信任”。笔记本的布局看起来与浏览器中的略有不同,以匹配 VS Code 的其余部分,但除此之外,体验完全相同,包括所有键盘快捷键。让我们通过 Shift+Enter 运行前三个单元格。这将启动 Jupyter 笔记本服务器(如果尚未运行,您将在笔记本的右上方看到状态)。运行单元格后,点击笔记本顶部菜单中的计算器按钮:这将打开变量资源管理器,在其中您可以查看当前存在的所有变量的值,如 图 B-2。也就是说,您只会找到已运行单元格中的变量。
在 VS Code 中保存 Jupyter 笔记本
要在 VS Code 中保存笔记本,您需要使用笔记本顶部的“保存”按钮,或在 Windows 上按 Ctrl+S,macOS 上按 Command-S。File > Save 不起作用。
图 B-2. Jupyter 笔记本变量资源管理器
如果您使用像嵌套列表、NumPy 数组或数据框等数据结构,您可以双击变量:这将打开数据查看器,并为您提供熟悉的类似电子表格的视图。双击 df
变量后,图 B-3 显示了数据查看器的内容。
图 B-3. Jupyter 笔记本数据查看器
虽然 VS Code 允许您运行标准的 Jupyter 笔记本文件,但它也允许您将笔记本转换为普通的 Python 文件——而不会丢失您的单元格。让我们看看它是如何工作的!
使用带有代码单元格的 Python 脚本
要在标准 Python 文件中使用 Jupyter 笔记本单元格,VS Code 使用特殊注释来标记单元格:# %%
。要转换现有的 Jupyter 笔记本,请打开它并点击笔记本顶部菜单中的“导出”按钮;参见 图 B-2。这将允许您从命令面板中选择“Python 文件”。但是,与其转换现有文件,不如创建一个名为 cells.py
的新文件,并包含以下内容:
# %%``3``+``4``# %% [markdown]``# # 这是一个标题``#``# 一些 markdown 内容
Markdown 单元格需要以 # %% [markdown]
开头,并要求整个单元格被标记为注释。如果您想将这样的文件作为笔记本运行,请在悬停在第一个单元格上时点击“下面运行”链接。这将打开右侧的 Python 交互窗口,如 图 B-4 所示。
图 B-4. Python 交互窗口
Python 交互窗口再次显示为笔记本。要以 ipynb 格式导出您的文件,请点击 Python 交互窗口顶部的保存图标(导出为 Jupyter 笔记本)。Python 交互窗口还为您提供了一个底部单元格,您可以在其中交互执行代码。与 Jupyter 笔记本相比,使用常规 Python 文件允许您使用 VS Code 调试器,并使得与版本控制的工作更加轻松,因为输出单元格(通常在版本之间添加大量噪音)将被忽略。
附录 C. 高级 Python 概念
在这个附录中,我们将更详细地探讨以下三个主题:类和对象、时区感知日期时间对象以及可变 vs. 不可变对象。这些主题是相互独立的,因此您可以按任意顺序阅读它们。
类和对象
在本节中,我们将编写自己的类以更好地理解类和对象之间的关系。类定义新的对象类型:类就像您用来烤蛋糕的烤盘。根据您使用的成分,您会得到不同的蛋糕,例如巧克力蛋糕或奶酪蛋糕。从烤模(类)中获取蛋糕(对象)的过程称为实例化,这也是为什么对象也称为类实例。无论是巧克力还是奶酪蛋糕,它们都是蛋糕的一种类型:类允许您定义保持相关数据(属性)和函数(方法)的新数据类型,并因此帮助您结构化和组织您的代码。现在让我回到来自第三章的汽车赛车游戏示例,以定义我们自己的类:
In``[``1``]:``class``Car``:``def
__init__``(``self``,``color``,``speed``=``0``):``self``.``color``=``color``self``.``speed``=``speed``def``accelerate``(``self``,``mph``):``self``.``speed``+=``mph
这是一个简单的汽车类,有两个方法。方法是类定义的一部分的函数。这个类有一个普通方法叫做 accelerate
。这个方法会改变一个类实例的数据(speed
)。它还有一个以双下划线开始和结束的特殊方法叫做 __init__
。当对象初始化时,Python 会自动调用它来附加一些初始数据到对象上。每个方法的第一个参数代表类的实例,按照约定称为 self
。当你看到如何使用 Car
类时,这将更清楚。首先,让我们实例化两辆汽车。你可以像调用函数一样调用类:通过添加括号并提供 __init__
方法的参数。你从不为 self
提供任何内容,因为 Python 会处理。在本示例中,self
将分别是 car1
或 car2
:
In``[``2``]:``# 让我们实例化两个汽车对象``car1``=``Car``(``"red"``)``car2``=``Car``(``color``=``"blue"``)
当你调用一个类时,实际上是在调用 __init__
函数,这就是为什么函数参数的一切都适用于此处:对于 car1
,我们将参数作为位置参数提供,而对于 car2
,我们使用关键字参数。在从 Car
类实例化两个汽车对象之后,我们将查看它们的属性并调用它们的方法。正如我们将看到的那样,在加速 car1
后,car1
的速度改变了,但对于 car2
则保持不变,因为这两个对象是彼此独立的:
In``[``3``]:``# 默认情况下,对象打印其内存位置``car1
Out[3]: <__main__.Car at 0x7fea812e3890>
In``[``4``]:``# 属性允许您访问对象的数据``car1``.``color
Out[4]: 'red'
In``[``5``]:``car1``.``speed
Out[5]: 0
In``[``6``]:``# 调用 car1 上的加速方法``car1``.``accelerate``(``20``)
`In
[
7]:
# car1 的速度属性发生了变化car1
.speed
Out[7]: 20
`In
[
8]:
# car2 的速度属性保持不变car2
.speed
Out[8]: 0
Python 还允许您直接更改属性,而无需使用方法:
`In
[
9]:
car1.
color=
"green"```In
[
10]:
car1.
color``
Out[10]: 'green'
In``[``11``]:``car2``.``color``# 未更改
Out[11]: 'blue'
总结一下: 类定义了对象的属性和方法。类允许您将相关的函数(“方法”)和数据(“属性”)组合在一起,以便可以通过点表示法方便地访问它们: myobject.attribute
或 myobject.method()
。
使用时区感知的 datetime 对象
在第三章中,我们简要介绍了无时区感知的 datetime 对象。如果时区很重要,通常在 UTC 时区中工作,仅在显示目的时转换为本地时区。UTC 代表协调世界时,是格林威治平均时间(GMT)的后继者。在使用 Excel 和 Python 时,您可能希望将 Excel 提供的无时区时间戳转换为时区感知的 datetime 对象。在 Python 中支持时区时,可以使用 dateutil 包,它不是标准库的一部分,但已预装在 Anaconda 中。以下示例展示了在处理 datetime 对象和时区时的几种常见操作:
`In
[
12]:
importdatetime
asdt
fromdateutil
importtz
`In
[
13]:
# 无时区的 datetime 对象timestamp
=dt
.datetime
(2020
,1
,31
,14
,30
)timestamp
.isoformat
()``
Out[13]: '2020-01-31T14:30:00'
`In
[
14]:
# 时区感知的 datetime 对象timestamp_eastern
=dt
.datetime
(2020
,1
,31
,14
,30
,tzinfo
=tz
.gettz
("US/Eastern"
))# 使用 isoformat 打印使其易于
# 看出与 UTC 的偏移timestamp_eastern
.isoformat
()``
Out[14]: '2020-01-31T14:30:00-05:00'
In``[``15``]:``# 将时区分配给无时区的 datetime 对象``timestamp_eastern``=``timestamp``.``replace``(``tzinfo``=``tz``.``gettz``(``"US/Eastern"``))``timestamp_eastern``.``isoformat``()
Out[15]: '2020-01-31T14:30:00-05:00'
In``[``16``]:``# 从一个时区转换到另一个时区。``# 由于 UTC 时区非常常见,``# 这里有一个快捷方式: tz.UTC``timestamp_utc``=``timestamp_eastern``.``astimezone``(``tz``.``UTC``)``timestamp_utc``.``isoformat``()
Out[16]: '2020-01-31T19:30:00+00:00'
In``[``17``]:``# 从时区感知到无时区``timestamp_eastern``.``replace``(``tzinfo``=``None``)
Out[17]: datetime.datetime(2020, 1, 31, 14, 30)
`In
[
18]:
# 当前没有时区的时间dt
.datetime
.now
()```Out[18]: datetime.datetime(2021, 1, 3, 11, 18, 37, 172170)``
In``[``19``]:``# 当前 UTC 时区时间``dt``.``datetime``.``now``(``tz``.``UTC``)
Out[19]: datetime.datetime(2021, 1, 3, 10, 18, 37, 176299, tzinfo=tzutc())
使用 Python 3.9 处理时区
Python 3.9 在标准库中以
timezone
模块的形式添加了适当的时区支持。使用它替换来自dateutil
的tz.gettz
调用:
from``zoneinfo``import``ZoneInfo``timestamp_eastern``=``dt``.``datetime``(``2020``,``1``,``31``,``14``,``30``,``tzinfo``=``ZoneInfo``(``"US/Eastern"``))
Python 中的可变与不可变对象
在 Python 中,可以更改其值的对象称为可变对象,而不能更改的称为不可变对象。表 C-1 展示了不同数据类型的分类方式。
表 C-1. 可变和不可变数据类型
可变性 | 数据类型 |
---|---|
可变 | 列表、字典、集合 |
不可变 | 整数、浮点数、布尔值、字符串、日期时间、元组 |
了解这种差异很重要,因为可变对象的行为可能与您从其他语言(包括 VBA)中习惯的行为不同。看一下以下的 VBA 片段:
Dim``a``As``Variant``,``b``As``Variant``a``=``Array``(``1``,``2``,``3``)``b``=``a``a``(``1``)``=``22``Debug``.``Print``a``(``0``)``&``", "``&``a``(``1``)``&``", "``&``a``(``2``)``Debug``.``Print``b``(``0``)``&``", "``&``b``(``1``)``&``", "``&``b``(``2``)
这将打印以下内容:
1, 22, 3 1, 2, 3
现在让我们用一个列表在 Python 中做同样的例子:
`In
[
20]:
a=
[1
,2
,3
]b
=a
a[
1]
=22
print(
a)
print(
b)
[1, 22, 3] [1, 22, 3]
在这里发生了什么?在 Python 中,变量是“附加”到对象的名称。通过b = a
,您将两个名称都附加到同一个对象上,即列表[1, 2, 3]
。因此,附加到该对象的所有变量将显示列表的更改。但这仅适用于可变对象:如果用不可变对象(如元组)替换列表,修改a
不会影响b
。如果希望像b
这样的可变对象独立于a
的更改,则必须显式地复制列表:
`In
[
21]:
a=
[1
,2
,3
]b
=a
.copy
()```In
[
22]:
a``
Out[22]: [1, 2, 3]
`In
[
23]:
b``
Out[23]: [1, 2, 3]
`In
[
24]:
a[
1]
=22
# 修改 "a"...`In
[
25]:
a``
Out[25]: [1, 22, 3]
`In
[
26]:
b# ...不会影响 "b"
Out[26]: [1, 2, 3]
通过使用列表的copy
方法,您创建的是浅拷贝:您会得到列表的一个副本,但如果列表包含可变元素,则这些元素仍然是共享的。如果您想递归地复制所有元素,则需要使用标准库中的copy
模块进行深拷贝:
`In
[
27]:
importcopy
b=
copy.
deepcopy(
a)
现在让我们看看当您将可变对象用作函数参数时会发生什么。
调用带有可变对象作为参数的函数
如果您来自 VBA,您可能习惯于将函数参数标记为传递引用(ByRef
)或传递值(ByVal
):当您将变量作为参数传递给函数时,函数将具有更改它(ByRef
)或将在值的副本上工作(ByVal
)的能力,从而保留原始变量不变。ByRef
是 VBA 中的默认值。考虑以下 VBA 中的函数:
Function``increment``(``ByRef``x``As``Integer``)``As``Integer``x``=``x``+``1``increment``=``x``End``Function
然后,像这样调用函数:
Sub``call_increment``()``Dim``a``As``Integer``a``=``1``Debug``.``Print``increment``(``a``)``Debug``.``Print``a``End``Sub
这将打印如下内容:
2 2
但是,如果您将increment
函数中的ByRef
更改为ByVal
,它将打印:
2 1
这在 Python 中是如何工作的?当您传递变量时,您传递指向对象的名称。这意味着行为取决于对象是否可变。让我们首先使用不可变对象:
In``[``28``]:``def``increment``(``x``):``x``=``x``+``1``return``x
In``[``29``]:``a``=``1``print``(``increment``(``a``))``print``(``a``)
2 1
现在让我们使用可变对象重复示例:
In``[``30``]:``def``increment``(``x``):``x``[``0``]``=``x``[``0``]``+``1``return``x
In``[``31``]:``a``=``[``1``]``print``(``increment``(``a``))``print``(``a``)
[2] [2]
如果对象是可变的,并且您希望保留原始对象不变,则需要传入对象的副本:
In``[``32``]:``a``=``[``1``]``print``(``increment``(``a``.``copy``()))``print``(``a``)
[2] [1]
另一个要注意的情况是在函数定义中使用可变对象作为默认参数的情况——让我们看看为什么!
函数中的可变对象作为默认参数
编写函数时,通常不应将可变对象用作默认参数。原因是默认参数的值仅在函数定义时评估一次,而不是在每次调用函数时。因此,将可变对象用作默认参数可能会导致意外行为:
In``[``33``]:``# 不要这样做:``def``add_one``(``x``=``[]):``x``.``append``(``1``)``return``x
In``[``34``]:``add_one``()
Out[34]: [1]
In``[``35``]:``add_one``()
Out[35]: [1, 1]
如果您想使用空列表作为默认参数,请改用以下方法代替:
In``[``36``]:``def``add_one``(``x``=``None``):``if``x``is``None``:``x``=``[]``x``.``append``(``1``)``return``x
In``[``37``]:``add_one``()
Out[37]: [1]
In``[``38``]:``add_one``()
Out[38]: [1]
索引
符号
-
%%time 单元格魔法,并行读取工作表
-
%%timeit 单元格魔法,并行读取工作表
A
-
绝对路径,Anaconda Prompt,Backend
-
激活 Conda 环境,创建新的 Conda 环境
-
ActiveX 控件,跨平台兼容性,RunPython 函数
-
活动栏(VS Code),安装和配置
-
插件(Excel)
-
自定义,独立工作簿:摆脱 xlwings Add-in,UDF 快速入门
-
安装 xlwings,Excel Add-in-Excel Add-in
-
-
添加
-
列到 DataFrame,通过添加新列设置数据
-
元素到列表,列表
-
Python 包追踪器中的包,我们将构建的内容-我们将构建的内容
-
-
add_package 函数(Python 包追踪器)示例,后端-后端
-
agg 方法(pandas),分组
-
aggfunc 函数(pandas),数据透视和溶解
-
模块的别名,模块和 import 语句
-
Altair,Plotly
-
Anaconda
-
组成部分,开发环境
-
Conda(参见 Conda)
-
安装,安装-安装
-
目的,开发环境
-
-
Anaconda Prompt
-
命令列表,Anaconda Prompt
-
交互式 Python 会话
-
结束,Python REPL:交互式 Python 会话
-
开始,Python REPL:交互式 Python 会话-Python REPL:交互式 Python 会话
-
-
长文件路径,运行 Python 脚本
-
注释,Python REPL:交互式 Python 会话
-
操作概述,Anaconda Prompt-Anaconda Prompt
-
目的,开发环境
-
Python 脚本,运行,运行 Python 脚本
-
运行
-
在 macOS 中,Anaconda Prompt
-
在 Windows 中,Anaconda Prompt
-
-
VS Code,运行,运行 Python 脚本
-
xlwings CLI
-
addin install 命令,Excel Add-in
-
目的,Excel Add-in
-
快速启动命令,快速启动命令
-
-
-
杀毒软件,xlwings 安装与,Excel Add-in
-
应用程序编程接口(API),Web API
-
应用对象(xlwings),Excel 对象模型,Excel 对象模型,应用属性
-
追加方法(OpenPyXL),使用 OpenPyXL 进行写入
-
AppleScript,xlwings 基础
-
应用程序编程接口(API),Web API
-
应用结构(Python 包追踪器),应用结构
-
后端,后端-后端
-
调试,调试-调试
-
前端,前端-前端
-
-
应用程序,层次结构,关注点分离-关注点分离
-
applymap 方法(pandas),应用函数-应用函数
-
arange 函数(NumPy),有用的数组构造函数
-
参数装饰器(xlwings),处理数据框和动态数组,子装饰器
-
参数,作为可变对象,将可变对象作为参数调用函数-将可变对象作为默认参数的函数
-
算术运算
-
在数据框上,算术运算-算术运算
-
在 NumPy 数组上,向量化和广播-向量化和广播
-
-
算术运算符(pandas),对应方法,算术运算
-
数组范围,有用的数组构造函数
-
基于数组的公式(xlwings),最小化跨应用程序调用
-
数组(NumPy)
-
广播,向量化和广播-向量化和广播
-
构造函数,有用的数组构造函数
-
数据分析问题,结论
-
数据类型,NumPy 数组-NumPy 数组
-
获取和设置元素,获取和设置数组元素-获取和设置数组元素
-
运行概览,NumPy 数组-NumPy 数组
-
通用函数 (ufuncs),通用函数 (ufunc)-通用函数 (ufunc)
-
向量化,向量化和广播-向量化和广播
-
视图与副本的区别,视图 vs. 副本-视图 vs. 副本
-
-
asfreq 方法 (pandas),重采样
-
属性
-
帮助文档,字符串
-
目的,属性和方法, 类和对象-类和对象
-
-
增强赋值符号,for 和 while 循环
-
禁用 Conda 环境的自动激活,禁用自动激活
-
自动完成,Visual Studio Code
-
自适应调整方法 (xlwings),案例研究 (再次审视):Excel 报告
-
Excel 中的自动化 (参见 xlwings)
B
-
后端
-
目的,前端
-
在 Python Package Tracker 中,后端-后端
-
-
编程最佳实践
-
DRY 原则,DRY 原则
-
关注点分离,关注点分离-关注点分离
-
测试,测试
-
版本控制,版本控制-版本控制
-
-
大数据,前言, pandas 的限制, 处理大型 Excel 文件-并行读取工作表
-
Binder, 关闭 Jupyter 笔记本
-
Bokeh,Plotly
-
Book 类 (xlwings),Excel 对象模型
-
book object (xlwings), Excel 对象模型
-
books collection (xlwings), Excel 对象模型
-
布尔构造器,布尔值
-
布尔数据类型,布尔值-布尔值, read_excel 函数和 ExcelFile 类
-
布尔索引 (pandas)
-
选择数据的方式,通过布尔索引选择-通过布尔索引选择
-
通过布尔索引设置数据,通过布尔索引设置数据-通过布尔索引设置数据
-
-
布尔运算符,布尔值,通过布尔索引选择
-
break 语句,for 和 while 循环
-
断点(VS Code),设置,调试 UDFs
-
广播,向量化和广播-向量化和广播,算术运算
-
内置转换器(xlwings),转换器和选项
-
range 对象的内置选项(xlwings),转换器和选项
-
商业智能(参见 Power BI)
-
业务层,关注点分离
-
ByRef 函数参数(VBA),以可变对象作为参数调用函数-以可变对象作为参数调用函数
-
ByVal 函数参数(VBA),以可变对象作为参数调用函数-以可变对象作为参数调用函数
C
-
缓存装饰器,缓存
-
缓存,缓存-缓存
-
计算,单独为其设置层级,关注点分离
-
调用函数,函数,调用函数,以可变对象作为参数调用函数-以可变对象作为默认参数的函数
-
capitalize 方法(pandas),处理文本列
-
层叠样式表(CSS),格式化数据框的数据部分
-
Case 语句(VBA),字典
-
案例研究
-
Excel 报表,案例研究:Excel 报表-案例研究:Excel 报表,案例研究(重访):Excel 报表,案例研究(再次重访):Excel 报表-案例研究(再次重访):Excel 报表
-
Google Trends 案例研究
-
数据框架和动态数组, 处理数据框架和动态数组-处理数据框架和动态数组
-
调试 UDFs, 调试 UDFs-调试 UDFs
-
Google Trends 解释, 介绍 Google Trends-介绍 Google Trends
-
获取数据, 从 Google Trends 获取数据-从 Google Trends 获取数据
-
绘图数据, 使用 UDFs 绘图-使用 UDFs 绘图
-
-
Python 包追踪器
-
添加包, 我们将要构建什么-我们将要构建什么
-
应用程序结构, 应用程序结构
-
后端, 后端-后端
-
数据库, 数据库-SQL 注入
-
调试, 调试-调试
-
错误处理, 异常-异常
-
前端, 前端-前端
-
web APIs, Web APIs-Web APIs
-
-
-
cd 命令, Anaconda Prompt
-
单元格格式(xlwings),清除, 如何解决缺失功能-如何解决缺失功能
-
cell looping, excel.py 模块
-
cells(Jupyter 笔记本)
-
编辑模式与命令模式, 编辑 vs. 命令模式-编辑 vs. 命令模式
-
在 Python 脚本中, 带有代码单元的 Python 脚本
-
运营概述, 笔记本单元-笔记本单元
-
输出, 笔记本单元
-
运行顺序, 运行顺序重要
-
-
链接索引和切片操作, 切片, 获取和设置数组元素
-
更改
-
cell 类型(Jupyter 笔记本), 笔记本单元
-
目录, Anaconda Prompt
-
列分隔符(Excel), Excel 对象模型-Excel 对象模型
-
到父目录,Anaconda Prompt
-
-
图表(Excel),透视和融合
-
(另见图表)
-
在 OpenPyXL 中创建,使用 OpenPyXL 写入-使用 OpenPyXL 写入
-
在 XlsxWriter 中创建,XlsxWriter-XlsxWriter
-
在 xlwings 中创建,Excel 图表-Excel 图表
-
-
类继承,现代语言特性
-
类,属性和方法
-
实例化,datetime 类, 类和对象
-
对象和,类和对象-类和对象
-
-
清除单元格格式(xlwings),如何解决缺失功能-如何解决缺失功能
-
Jupyter 笔记本的云提供商,关闭 Jupyter 笔记本
-
代码块,代码块和 pass 语句-代码块和 pass 语句
-
代码单元格(Jupyter 笔记本),笔记本单元格
-
代码命令(VS Code),运行 Python 脚本
-
集合(xlwings)
-
图表(Excel),创建,Excel 图表-Excel 图表
-
定义名称(Excel),创建,定义名称-定义名称
-
图片(Excel),将 Matplotlib 绘图作为,图片:Matplotlib 绘图-图片:Matplotlib 绘图
-
目的,Excel 对象模型
-
-
颜色,十六进制值,使用 OpenPyXL 写入-使用 OpenPyXL 写入
-
列(pandas)
-
添加到 DataFrame,通过添加新列设置数据
-
用于 DataFrames,列-列,格式化 DataFrame 的索引和标头-格式化 DataFrame 的索引和标头
-
选择,按标签选择
-
-
COM(组件对象模型),xlwings 基础
-
合并 DataFrames
-
连接,连接-连接
-
连接,连接和合并-连接和合并
-
合并,连接和合并-连接和合并
-
-
命令历史记录,通过滚动查看,Anaconda 提示符
-
命令模式(Jupyter 笔记本),编辑模式与命令模式-编辑模式与命令模式
-
命令面板(VS Code),安装和配置
-
命令提示符(Windows),Anaconda 提示符
-
调试,调试
-
禁用 Conda 环境的自动激活,禁用自动激活
-
-
Anaconda 提示符中的命令列表,Anaconda 提示符
-
Python 中的注释,数学运算符
-
复杂数据类型,数值类型
-
复合键(数据库),包裹追踪数据库
-
pandas 中的 concat 函数,串联-串联,重置基础和相关性
-
串联
-
DataFrames,串联-串联
-
列表,列表
-
字符串,字符串
-
元组,元组
-
-
Conda,包管理器:Conda 和 pip-包管理器:Conda 和 pip
-
命令,包管理器:Conda 和 pip
-
环境,Conda 环境
-
创建,创建一个新的 Conda 环境-创建一个新的 Conda 环境
-
禁用自动激活,禁用自动激活
-
目的,Conda 环境
-
-
pip 对比,包管理器:Conda 和 pip
-
-
条件表达式,if 语句和条件表达式
-
Python Package Tracker 中的条件格式化,前端
-
针对 xlwings 的配置层次结构,配置层次结构
-
配置 VS Code,安装和配置-安装和配置
-
数据库连接,数据库连接-数据库连接
-
常量内存(XlsxWriter),使用 XlsxWriter 写入
-
数组构造函数(NumPy),有用的数组构造函数
-
上下文管理器,read_excel 函数和 ExcelFile 类
-
Continue 按钮(VS Code 调试器),调试器
-
continue 语句,for 和 while 循环
-
控制流
-
代码块,代码块和 pass 语句-代码块和 pass 语句
-
条件表达式,if 语句和条件表达式
-
字典推导式,列表、字典和集合推导式
-
for 循环,for 和 while 循环-for 和 while 循环
-
if 语句,if 语句和条件表达式-if 语句和条件表达式
-
列表推导式,列表、字典和集合推导式
-
pass 语句,代码块和 pass 语句-代码块和 pass 语句
-
集合推导式,列表、字典和集合推导式
-
while 循环,for 和 while 循环
-
-
转换器(xlwings),转换器和选项
-
转换
-
excel.py 模块中的数据类型,excel.py 模块-excel.py 模块
-
索引到列(pandas),索引
-
对象到布尔数据类型,read_excel 函数和 ExcelFile 类
-
字符串到日期时间数据类型,创建日期时间索引
-
-
协调世界时(UTC),处理时区,使用时区感知的 datetime 对象
-
副本(数据框)
-
方法返回的,索引
-
视图与,视图 vs. 副本
-
-
副本(NumPy),视图与,视图 vs. 副本-视图 vs. 副本
-
copy 方法,浅复制与深复制,可变 vs. 不可变的 Python 对象
-
相关方法(pandas),重新基准化和相关性
-
时间序列分析中的相关性,重新基准化和相关性-重新基准化和相关性
-
循环中的计数变量,for 和 while 循环
-
COVID-19 测试结果,延迟报告,Excel 新闻
-
CPU 时间,并行读取工作表
-
跨应用调用(xlwings),最小化,最小化跨应用调用, 最小化跨应用调用-最小化跨应用调用
-
跨平台兼容性,跨平台兼容性
-
CSE 数组(Excel),使用 DataFrames 和动态数组
-
CSS(层叠样式表),格式化 DataFrame 的数据部分
-
CSV 文件
-
将 DataFrame 数据导出为,导出 CSV 文件-导出 CSV 文件
-
导入到 DataFrames,导入 CSV 文件-导入 CSV 文件
-
-
cuDF,与 pandas 的限制
-
当前目录
-
在 Windows 中,列出文件在,Anaconda Prompt
-
在 macOS
-
列出文件在,Anaconda Prompt
-
查看完整路径,Anaconda Prompt
-
-
-
自定义插件(xlwings),独立工作簿:摆脱 xlwings 插件, UDF 快速入门
-
自定义函数(参见 UDFs)
D
-
Dask,与 pandas 的限制
-
数据对齐(pandas),使用 pandas 进行数据分析, 索引, 算术操作
-
数据分析表达式(DAX),前言
-
使用 pandas 进行数据分析(参见 pandas)
-
数据层,关注点分离
-
数据部分(DataFrames),格式化,格式化 DataFrame 的数据部分-格式化 DataFrame 的数据部分
-
数据结构
-
字典,字典-字典
-
列表,集合
-
列表,列表-列表
-
目的,数据结构
-
集合,集合-集合
-
元组,元组
-
在 VBA 中,数据结构
-
-
数据类型
-
布尔值,布尔值-布尔值,read_excel 函数和 ExcelFile 类
-
使用 excel.py 模块转换,excel.py 模块-excel.py 模块
-
在数据帧中,数据帧和系列
-
datetime,字符串转换为,创建 DatetimeIndex
-
可变与不可变,Python 中可变与不可变对象
-
数值,数值类型-数值类型
-
NumPy 数组,NumPy 数组-NumPy 数组
-
目的,数据类型
-
字符串,字符串-字符串
-
-
Python 包跟踪器中的数据验证,前端
-
数据查看器(VS Code),运行 Jupyter 笔记本
-
数据查看器,Excel 作为,使用 Excel 作为数据查看器-使用 Excel 作为数据查看器
-
database.py 示例,后端
-
数据库
-
连接,数据库连接-数据库连接
-
SQL 注入,SQL 注入-SQL 注入
-
SQL 查询,SQL 查询-SQL 查询
-
Python 包跟踪器案例研究结构,包跟踪器数据库-包跟踪器数据库
-
类型,数据库-数据库
-
-
数据帧
-
应用函数,应用函数-应用函数
-
算术运算,算术运算-算术运算
-
列,列-列
-
连接,连接-连接
-
拷贝
-
方法返回,索引
-
视图与,视图与副本
-
-
创建,数据帧和系列
-
描述性统计,描述性统计-描述性统计
-
重复数据,重复数据-重复数据
-
Excel 电子表格与,数据框和系列-数据框和系列
-
探索方法,导入 CSV 文件
-
导出
-
作为 CSV 文件,导出 CSV 文件-导出 CSV 文件
-
目的,导入和导出数据框
-
-
在 Excel 中格式化,在 Excel 中格式化数据框-格式化数据框的数据部分
-
在 Google Trends 案例研究中,操作数据框和动态数组-操作数据框和动态数组
-
分组数据,分组
-
导入
-
作为 CSV 文件,导入 CSV 文件-导入 CSV 文件
-
目的,导入和导出数据框
-
-
索引,索引-索引
-
连接,连接和合并-连接和合并
-
限制,pandas 的限制
-
融合数据,数据透视和融合
-
合并,连接和合并-连接和合并
-
缺失数据,缺失数据-缺失数据
-
透视数据,数据透视和融合-数据透视和融合
-
绘图
-
绘图库列表,Plotly
-
使用 Matplotlib,Matplotlib-Matplotlib
-
使用 Plotly,Plotly-Plotly
-
目的,绘图
-
-
在 Excel 中使用 xlwings 进行读写,操作数据框-操作数据框
-
选择数据
-
使用布尔索引,通过布尔索引进行选择-通过布尔索引进行选择
-
按标签,按标签选择-按标签选择
-
使用 MultiIndexes,使用 MultiIndex 进行选择-使用 MultiIndex 进行选择
-
按位置,按位置选择-按位置选择
-
-
Series 对比,DataFrame 和 Series-DataFrame 和 Series,按标签选择
-
设置数据
-
通过添加列,通过添加新列设置数据
-
通过布尔索引,通过布尔索引设置数据-通过布尔索引设置数据
-
按标签,按标签或位置设置数据
-
按位置,按标签或位置设置数据
-
通过替换值,通过替换值设置数据
-
-
SQL 查询对比,SQL 查询
-
文本列,处理文本列
-
时间序列分析(参见时间序列分析)
-
转置,列
-
-
日期序列号(Excel),datetime 类
-
datetime 数据类型,将字符串转换为,创建一个 DatetimeIndex
-
datetime 模块,datetime 类-datetime 类,处理时区感知的 datetime 对象-处理时区感知的 datetime 对象
-
DatetimeIndex(pandas)
-
创建,创建一个 DatetimeIndex-创建一个 DatetimeIndex
-
过滤,过滤 DatetimeIndex-过滤 DatetimeIndex
-
时区在,处理时区-处理时区
-
-
dateutil 包,处理时区感知的 datetime 对象
-
date_range 函数(pandas),创建一个 DatetimeIndex
-
DAX(数据分析表达式),前言
-
停用 Conda 环境,创建一个新的 Conda 环境
-
调试
-
Python 包追踪器,调试-调试
-
在文本编辑器中,Visual Studio Code
-
UDFs,调试 UDFs-调试 UDFs
-
在 VS Code 中,调试器-调试器
-
-
十进制数据类型,数值类型
-
声明性语言,SQL 查询
-
装饰器(xlwings)
-
的顺序,使用数据框和动态数组工作
-
的目的,UDF 快速入门
-
子装饰器,子装饰器-子装饰器
-
-
深度拷贝,可变 vs. 不可变 Python 对象
-
def 关键字,定义函数
-
默认参数,可变对象作为,具有可变对象作为默认参数的函数
-
定义名称(Excel),在 xlwings 中创建,定义名称-定义名称
-
定义函数,定义函数
-
del 语句(列表),列表
-
删除
-
列(pandas),列
-
Conda 环境,创建一个新的 Conda 环境
-
从列表中提取元素,列表
-
-
依赖项
-
xlwings 部署, Python 依赖-Python 依赖
-
在 xlwings 中,xlwings 基础
-
-
部署
-
已定义,部署
-
关于 xlwings
-
配置层次结构,配置层次结构
-
Python 依赖项,Python 依赖-Python 依赖
-
设置,设置-设置
-
独立工作簿,独立工作簿:摆脱 xlwings 插件-独立工作簿:摆脱 xlwings 插件
-
-
-
describe.py 示例,使用数据框和动态数组工作
-
描述统计,描述统计-描述统计
-
确定性函数,缓存
-
开发环境(参见 Anaconda; Jupyter 笔记本; VS Code)
-
字典构造函数,集合
-
字典,字典-字典
-
字典推导式,列表、字典和集合推导式
-
dir 命令,Anaconda 命令提示符
-
目录
-
更改,Anaconda 命令提示符
-
当前目录
-
在 Anaconda 命令提示符 中列出文件
-
查看完整路径,Anaconda 命令提示符
-
-
父目录,切换到,Anaconda 提示符
-
-
禁用 Conda 环境的自动激活,禁用自动激活
-
文档字符串,PEP 8:Python 代码风格指南
-
文档,字符串
-
点符号表示法,属性和方法
-
降采样,重采样
-
驱动程序(用于数据库),数据库连接-数据库连接
-
dropna 方法(pandas),缺失数据
-
drop_duplicates 方法(pandas),重复数据
-
DRY 原则,DRY 原则
-
数据框中的重复数据,重复数据-重复数据
-
duplicated 方法(pandas),重复数据
-
动态数组(Excel),使用数据框和动态数组工作-使用数据框和动态数组工作
-
动态类型,变量
E
-
编辑模式(Jupyter 笔记本),编辑 vs. 命令模式-编辑 vs. 命令模式
-
编辑文件(Excel)
-
使用 OpenPyXL,使用 OpenPyXL 进行编辑-使用 OpenPyXL 进行编辑
-
使用 xlutils,使用 xlutils 进行编辑
-
-
数组元素(NumPy),获取和设置,获取和设置数组元素-获取和设置数组元素
-
Emacs,运行 Python 脚本
-
启用宏(Excel),RunPython 函数
-
结束交互式 Python 会话,Python REPL:交互式 Python 会话
-
端点,Web API
-
引擎参数(read_excel 或 to_excel 函数),何时使用哪个包
-
enumerate 函数,for 和 while 循环
-
环境变量,设置
-
错误处理
-
在 Python 包追踪器中,异常-异常
-
在 VBA 中,现代语言特性
-
-
字符串中的转义字符,字符串
-
EuSpRIG(欧洲电子表格风险兴趣组),Excel 新闻
-
示例
-
add_package 函数(Python 包追踪器),后端-后端
-
database.py,后端
-
describe.py,与数据框和动态数组一起工作
-
first_project.py,运行主程序-运行主程序
-
first_udf.py,UDF 快速入门
-
get_interest_over_time 函数(google_trends.py),从 Google 趋势获取数据-从 Google 趋势获取数据
-
importsub.py,子装饰器
-
pep8_sample.py,PEP 8:Python 代码风格指南-PEP 8:Python 代码风格指南
-
plot 函数(google_trends.py),使用 UDFs 绘图-使用 UDFs 绘图
-
revenues.py,最小化跨应用程序调用
-
sales_report_pandas.py,案例研究:Excel 报告-案例研究:Excel 报告
-
sales_report_xlwings.py,案例研究(再次审视):Excel 报告
-
temperature.py,模块和导入语句
-
-
Excel
-
基于数组的计算,向量化和广播
-
自动化(见 xlwings)
-
图表,数据透视和融合
-
(见图表)
-
在 OpenPyXL 中创建,使用 OpenPyXL 写入-使用 OpenPyXL 写入
-
在 XlsxWriter 中创建,XlsxWriter-XlsxWriter
-
在 xlwings 中创建,Excel 图表-Excel 图表
-
-
作为数据查看器,使用 Excel 作为数据查看器-使用 Excel 作为数据查看器
-
使用 xlwings 读写数据框,使用数据框-使用数据框
-
日期序列号,datetime 类
-
在 xlwings 中创建定义名称,定义名称-定义名称
-
文件(见文件)
-
浮点数在,数值类型
-
在 Excel 中格式化数据框,在 Excel 中格式化数据框-格式化数据框的数据部分
-
作为前端
-
安装 xlwings 插件,Excel 插件-Excel 插件
-
用途,使用 Excel 作为前端与 xlwings
-
快速启动命令,快速启动命令
-
运行主按钮,运行主按钮-运行主按钮
-
RunPython 函数,RunPython 函数-不使用快速启动命令的 RunPython
-
-
历史,为什么选择 Python 用于 Excel?
-
实例,Excel 对象模型, Excel 对象模型
-
Jupyter 笔记本与之对比,Jupyter 笔记本
-
语言和区域设置,Excel 对象模型-Excel 对象模型
-
对数,偏移和百分比变化
-
macOS 权限,使用 Excel 作为数据查看器
-
现代 Excel,现代 Excel
-
名称管理器,定义名称
-
在新闻报道中,Excel 在新闻中-Excel 在新闻中
-
对象模型,Excel 对象模型-Excel 对象模型
-
图片,Matplotlib 绘图作为,图片:Matplotlib 绘图-图片:Matplotlib 绘图
-
作为编程语言,Excel 是一种编程语言-Excel 是一种编程语言
-
编程,读写文件对比,本书的组织方式
-
Python 在 Excel 中的优势,Python 用于 Excel
-
跨平台兼容性,跨平台兼容性
-
现代语言特性,现代语言特性-现代语言特性
-
可读性,可读性和可维护性-可读性和可维护性
-
科学计算,科学计算
-
标准库和包管理器,标准库和包管理器-标准库和包管理器
-
-
汇报案例研究,案例研究:Excel 报告-案例研究:Excel 报告,案例研究(重访):Excel 报告,案例研究(再次重访):Excel 报告-案例研究(再次重访):Excel 报告
-
电子表格,数据框与之对比,数据框和系列-数据框和系列
-
独立工作簿,独立工作簿:摆脱 xlwings 插件-独立工作簿:摆脱 xlwings 插件
-
时间序列分析的局限性,使用 pandas 进行时间序列分析
-
信任对 VBA 项目模型的访问设置,UDF 入门
-
使用范围,使用 xlrd 读取
-
版本控制,版本控制-版本控制
-
版本,Python 和 Excel 版本-Python 和 Excel 版本
-
僵尸进程,xlwings 基础
-
-
excel.py 模块,excel.py 模块-excel.py 模块
-
ExcelFile 类(pandas),read_excel 函数和 ExcelFile 类-read_excel 函数和 ExcelFile 类
-
ExcelWriter 类(pandas),to_excel 方法和 ExcelWriter 类-to_excel 方法和 ExcelWriter 类
-
异常(参见错误处理)
-
expand 方法(xlwings),处理数据框
-
导出 DataFrame 数据
-
作为 CSV 文件,导出 CSV 文件-导出 CSV 文件
-
目的是,导入和导出数据框
-
-
从 xlsm 文件中提取宏代码(Xlsxwriter),XlsxWriter-XlsxWriter
F
-
f-strings(格式化字符串字面值),字符串
-
假布尔数据类型,布尔值-布尔值
-
ffill 方法(pandas),重采样
-
图形对象(Matplotlib),图片:Matplotlib 绘图-图片:Matplotlib 绘图
-
文件扩展名,查看,Anaconda 命令提示符-Anaconda 命令提示符
-
文件路径
-
绝对路径与相对路径,后端
-
文件匹配,案例研究:Excel 报告
-
Path 类,案例研究:Excel 报告
-
在 Windows 中作为原始字符串,导出 CSV 文件
-
-
文件
-
在当前目录中,列出,Anaconda Prompt
-
编辑
-
使用 OpenPyXL,使用 OpenPyXL 进行编辑-使用 OpenPyXL 进行编辑
-
使用 xlutils,使用 xlutils 进行编辑
-
-
读取
-
pandas 的局限性,在使用 Excel 文件时的限制
-
使用 OpenPyXL,使用 OpenPyXL 进行读取-使用 OpenPyXL 进行写入,使用 OpenPyXL 进行读取
-
使用 pandas,read_excel 函数和 ExcelFile 类-read_excel 函数和 ExcelFile 类
-
并行处理,并行读取工作表-并行读取工作表
-
编程 Excel 与,本书的组织方式
-
使用 pyxlsb,pyxlsb-pyxlsb
-
来自 URL,read_excel 函数和 ExcelFile 类
-
使用 xlrd,使用 xlrd 进行读取-使用 xlrd 进行读取,使用 xlrd 进行读取
-
-
写入
-
pandas 的局限性,在使用 Excel 文件时的限制
-
使用 OpenPyXL,使用 OpenPyXL 进行写入-使用 OpenPyXL 进行写入,使用 OpenPyXL 进行写入
-
使用 pandas,to_excel 方法和 ExcelWriter 类-to_excel 方法和 ExcelWriter 类
-
编程 Excel 与,本书的组织方式
-
使用 XlsxWriter,XlsxWriter-XlsxWriter,使用 XlsxWriter 进行写入
-
使用 xlwt,使用 xlwt 进行写入-使用 xlutils 进行编辑
-
-
-
fillna 方法(pandas),缺失数据
-
过滤
-
数据框(DataFrames),通过布尔索引选择-通过布尔索引选择
-
DatetimeIndex(pandas),过滤 DatetimeIndex-过滤 DatetimeIndex
-
-
first_project.py 示例,运行主按钮-运行主按钮
-
first_udf.py 示例,UDF 快速开始
-
flake8,PEP 8 和 VS Code
-
浮点数据类型,数值类型-数值类型
-
float64 数据类型(NumPy),NumPy 数组-NumPy 数组
-
浮点数不准确性,数值类型
-
浮点数,数值类型
-
For Each 语句(VBA),for 和 while 循环
-
for 循环,for 和 while 循环-for 和 while 循环
-
外键(数据库),包裹追踪器数据库,SQL 查询
-
表单控件(Excel),RunPython 函数
-
格式化
-
Excel 中的数据框,在 Excel 中格式化数据框-格式化数据框的数据部分
-
将日期时间对象转换为字符串,datetime 类
-
数据框中的字符串,应用函数-应用函数
-
-
前向填充,重采样
-
分数数据类型,数值类型
-
前端
-
定义,使用 Excel 作为 xlwings 前端
-
Excel 作为
-
安装 xlwings 插件,Excel 插件-Excel 插件
-
目的,使用 Excel 作为 xlwings 前端
-
快速开始命令,快速开始命令
-
运行主按钮,运行主按钮-运行主按钮
-
RunPython 函数,RunPython 函数-RunPython 无快速开始命令
-
-
目的,前端
-
在 Python 包追踪器中,前端-前端
-
-
冻结的可执行文件,Python 依赖
-
全外连接,连接和合并
-
函数
-
应用于数据框,应用函数-应用函数
-
调用, 函数, 调用函数, 使用可变对象作为参数调用函数-使用可变对象作为默认参数的函数
-
装饰器 (xlwings)
-
顺序, 处理数据框和动态数组
-
功能的目的, UDF 快速入门
-
子装饰器, 子装饰器-子装饰器
-
-
定义, 定义函数
-
确定性, 缓存
-
Lambda 表达式, 应用函数
-
功能的目的, 函数
-
重新计算 (Excel), UDF 快速入门
-
通用函数 (NumPy), 通用函数 (ufunc)-通用函数 (ufunc)
-
用户定义的函数 (参见 UDFs)
-
VBA, DRY 原则, 运行 VBA 代码
-
易变的(Excel),UDF 快速入门
-
-
functools 模块, 缓存
G
-
获取方法 (字典), 字典
-
GET 请求, Web API
-
获取数组元素 (NumPy), 获取和设置数组元素-获取和设置数组元素
-
get_interest_over_time 函数 (google_trends.py) 示例, 从 Google 趋势获取数据-从 Google 趋势获取数据
-
Git, 版本控制-版本控制
-
文件名匹配, 案例研究:Excel 报告
-
Google Colab, 关闭 Jupyter 笔记本
-
Python 的 Google 风格指南, PEP 8:Python 代码风格指南
-
Google 趋势案例研究
-
数据框和动态数组, 处理数据框和动态数组-处理数据框和动态数组
-
调试用户定义函数, 调试用户定义函数-调试用户定义函数
-
Google 趋势的解释, 介绍 Google 趋势-介绍 Google 趋势
-
提取数据, 从 Google 趋势获取数据-从 Google 趋势获取数据
-
绘制数据,使用 UDF 进行绘图-使用 UDF 进行绘图
-
-
图形用户界面(参见前端)
-
网格线(Excel),隐藏,前端
-
分组 DataFrame 数据,分组
-
GUI(参见前端)
H
-
head 方法(pandas),导入 CSV 文件
-
DataFrame 列的标题,格式化 DataFrame 的索引和标题-格式化 DataFrame 的索引和标题
-
热力图(Plotly),重新基准和相关性
-
帮助文档,字符串
-
颜色的十六进制值,使用 OpenPyXL 进行编写-使用 OpenPyXL 进行编写
-
隐藏网格线(Excel),前端
-
HoloViews,Plotly
-
同质数据,NumPy 数组
-
恐怖故事网页,Excel 新闻
-
HTTP 状态码,Web API
I
-
Ibis,与 pandas 的限制
-
IDE(集成开发环境),Visual Studio Code,运行 Python 脚本-运行 Python 脚本
-
if 语句,if 语句和条件表达式-if 语句和条件表达式
-
iloc 属性(pandas),按位置选择-按位置选择,按标签或位置设置数据
-
不可变对象,元组,可变与不可变的 Python 对象-可变与不可变的 Python 对象
-
隐式换行,列表
-
import 语句,模块和 import 语句-模块和 import 语句
-
导入
-
DataFrame 数据
-
作为 CSV 文件,导入 CSV 文件-导入 CSV 文件
-
用途,导入和导出数据框
-
-
模块,模块和 import 语句-模块和 import 语句
- 运行脚本与,后端
-
UDF(用户定义函数),UDF 快速入门-UDF 快速入门
-
-
importsub.py 示例,装饰器 Sub
-
imshow 方法(Plotly),重基准化和相关性
-
in 操作符,列表,通过布尔索引选择
-
索引
-
在 Python 中,索引
-
范围对象(xlwings),Excel 对象模型
-
在 VBA 中,索引
-
零基和一基,Excel 对象模型
-
-
索引
-
对于 DataFrames,索引-索引,格式化 DataFrame 的索引和标题-格式化 DataFrame 的索引和标题
-
基于时间的,使用 pandas 进行时间序列分析
-
-
info 方法(pandas),DataFrame 和 Series,导入 CSV 文件,read_excel 函数和 ExcelFile 类
-
init 方法,类和对象-类和对象
-
初始化(对象的),类和对象
-
内连接,连接和合并
-
输入,独立的层,关注点分离
-
安装
-
Anaconda,安装-安装
-
包,包管理器:Conda 和 pip,创建新的 Conda 环境
-
Plotly,Plotly
-
pytrends,包管理器:Conda 和 pip
-
pyxlsb,包管理器:Conda 和 pip
-
VS Code,安装和配置
-
xlutils,xlrd、xlwt 和 xlutils
-
xlwings 插件,Excel 插件-Excel 插件
-
-
实例(Excel 的),Excel 对象模型,Excel 对象模型
-
实例化,属性和方法,datetime 类,类和对象
-
int 数据类型,数值类型-数值类型
-
整数,数值类型
-
集成开发环境(IDEs),Visual Studio Code,运行 Python 脚本-运行 Python 脚本
-
IntelliSense,Visual Studio Code
-
交互式 Python 会话
-
结束,Python REPL:交互式 Python 会话
-
开始,Python REPL:交互式 Python 会话-Python REPL:交互式 Python 会话
-
-
isin 操作符(pandas),通过布尔索引选择
-
isna 方法(pandas),缺失数据
-
items 方法,for 和 while 循环
J
-
JavaScript 对象表示法(JSON),Web APIs-Web APIs
-
连接条件,连接和合并
-
join 方法(pandas),连接和合并-连接和合并
-
加入
-
数据库表,数据库
-
数据帧,连接和合并-连接和合并
-
-
JSON(JavaScript 对象表示法),Web APIs-Web APIs
-
json 模块,Web APIs
-
Jupyter 内核,关闭 Jupyter 笔记本
-
Jupyter 笔记本
-
单元格
-
编辑模式与命令模式,编辑模式 vs. 命令模式-编辑模式 vs. 命令模式
-
运行概述,笔记本单元格-笔记本单元格
-
输出,笔记本单元格
-
在 Python 脚本中,带有代码单元格的 Python 脚本
-
运行顺序,运行顺序很重要
-
-
云提供商,关闭 Jupyter 笔记本
-
注释,数学运算符
-
Excel 与之比较,Jupyter 笔记本
-
魔术命令,Matplotlib
-
目的,开发环境,Jupyter 笔记本-Jupyter 笔记本
-
重命名,运行 Jupyter 笔记本
-
运行,运行 Jupyter 笔记本-运行 Jupyter 笔记本,运行 Jupyter 笔记本-运行 Jupyter 笔记本
-
在 VS Code 中保存,运行 Jupyter 笔记本
-
关闭,关闭 Jupyter 笔记本
-
VS Code 对比,Visual Studio Code
-
-
JupyterLab,运行 Python 脚本
K
-
Kaggle,关闭 Jupyter 笔记本
-
键/值组合(参见字典)
-
键盘快捷键
-
用于注释,数学运算符
-
Jupyter 笔记本,编辑 vs. 命令模式-编辑 vs. 命令模式
-
-
关键字参数(用于函数),调用函数
-
Koalas,与 pandas 的限制
-
Komodo IDE,运行 Python 脚本
L
-
标签
-
按标签选择数据(pandas),按标签选择-按标签选择
-
按标签或位置设置数据(pandas),按标签或位置设置数据
-
在 VBA 中,现代语言特性
-
-
lambda 表达式,应用函数
-
lambda 函数(Excel),Excel 是一种编程语言
-
语言设置(Excel),Excel 对象模型-Excel 对象模型
-
应用层次,关注点分离-关注点分离
-
左连接,连接和合并
-
len 函数(列表),列表
-
换行,列表
-
代码检查,PEP 8 和 VS Code
-
列表推导式,列表、字典和集合推导式
-
列表构造器,集合
-
列分隔符(Excel),更改,Excel 对象模型-Excel 对象模型
-
列出当前目录文件,Anaconda 提示符
-
列表,列表-列表
-
字面常量,集合,datetime 类
-
loc 属性(pandas),按标签选择-按标签选择,按标签或位置设置数据
-
对数收益率,移位和百分比变化-移位和百分比变化
-
记录 ufunc(NumPy),偏移和百分比变化
-
Excel 和 Python 中的对数,偏移和百分比变化
-
伦敦鲸鱼故事,Excel 新闻
-
循环
-
for 循环,for 和 while 循环-for 和 while 循环
-
while 循环,for 和 while 循环
-
-
Lotus 1-2-3,为什么选择 Python 处理 Excel?
-
低级包,excel.py 模块
-
小写,转换为/从大写,字符串
-
lru_cache 装饰器,缓存
-
ls 命令,Anaconda Prompt
-
lxml,使用 OpenPyXL 进行编写
M
-
M 公式语言(Power Query),前言
-
macOS
-
Anaconda Prompt,运行,Anaconda Prompt
-
当前目录
-
列出文件在,Anaconda Prompt
-
查看完整路径,Anaconda Prompt
-
-
提取宏代码(Xlsxwriter),XlsxWriter
-
文件扩展名,查看,Anaconda Prompt
-
列分隔符(Excel),更改,Excel 对象模型
-
权限在,将 Excel 用作数据查看器,Excel 对象模型
-
功能区添加,Excel 插件
-
终端,Anaconda Prompt-Anaconda Prompt
- 禁用 Conda 环境的自动激活,禁用自动激活
-
VS Code
-
配置,安装和配置
-
安装,安装和配置
-
-
xlwings 依赖,xlwings 基础
-
-
宏代码(Xlsxwriter),从 xlsm 文件中提取,XlsxWriter-XlsxWriter
-
宏对象(xlwings),运行 VBA 代码
-
宏
-
启用(Excel),RunPython 函数
-
在 Python 包追踪器中,前端
-
运行(Excel),RunPython 函数
-
-
魔术命令(Jupyter 笔记本),Matplotlib,并行读取表
-
Markdown 单元格(Jupyter 笔记本),笔记本单元格-笔记本单元格
-
数学运算符,数学运算符
-
Matplotlib,Matplotlib-Matplotlib
- 将图表绘制为 Excel 图片,图片:Matplotlib 图表-图片:Matplotlib 图表
-
矩阵,列表, 向量化和广播
-
均值方法(pandas),分组
-
melt 函数(pandas),数据透视和溶解
-
融化 DataFrame 数据,数据透视和溶解
-
记忆化,缓存
-
merge 方法(pandas),连接和合并-连接和合并
-
合并请求,版本控制
-
合并
-
DataFrames,连接和合并-连接和合并
-
字典,字典
-
-
方法链,索引
-
方法
-
pandas 对应的算术运算符,算术运算
-
探索 DataFrame,导入 CSV 文件
-
DataFrames 的副本返回,索引
-
帮助文档,字符串
-
目的,属性和方法, 类和对象-类和对象
-
DataFrames 中的字符串方法,处理文本列
-
-
Microsoft Access,数据库
-
迁移数据库,数据库
-
减少跨应用调用(xlwings),减少跨应用调用, 减少跨应用调用-减少跨应用调用
-
DataFrames 中的缺失数据,缺失数据-缺失数据
-
模型-视图-控制器(MVC),应用程序结构
-
现代 Excel,现代 Excel
-
Python 与 Excel 中的现代语言特性,现代语言特性-现代语言特性
-
Modin,与 pandas 的限制, 并行读取工作表
-
模块化,关注分离-关注分离
-
模块搜索路径(Python),RunPython 函数-RunPython 函数,设置
-
模块
-
datetime 模块,datetime 类-datetime 类,使用时区感知的 datetime 对象-使用时区感知的 datetime 对象
-
导入,模块和导入语句-模块和导入语句
- 运行脚本与,后端
-
时区模块,使用时区感知的 datetime 对象
-
-
MongoDB,数据库
-
时间序列分析中的移动平均线,滚动窗口
-
MultiIndexes(pandas),通过选择数据,通过 MultiIndex 选择数据-通过 MultiIndex 选择数据
-
Multiplan,为什么选择 Python 处理 Excel?
-
多进程,并行读取工作表-并行读取工作表
-
可变对象
-
作为函数参数,将可变对象作为参数调用函数-带有可变对象的默认参数函数
-
不可变对象与,可变 vs. 不可变 Python 对象-可变 vs. 不可变 Python 对象
-
-
MVC(模型-视图-控制器),应用结构
-
mypy,类型提示
-
MySQL,数据库
N
-
名称管理器(Excel),定义名称
-
命名范围
-
在 xlwings 中创建,定义名称-定义名称
-
在 Python Package Tracker 中,前端
-
-
脚本的命名空间,模块和导入语句
-
命名
-
列(pandas),列
-
索引(pandas),索引
-
脚本,模块和导入语句
-
变量,变量
-
-
NaN 值
-
读取 Excel 文件时处理,read_excel 函数和 ExcelFile 类
-
目的是,缺失数据-缺失数据
-
替换为零,算术运算
-
-
嵌套列表,列表, NumPy 数组
-
新闻故事,Excel 中的,新闻中的 Excel-新闻中的 Excel
-
None 内建常量,布尔值
-
标准化(数据库),数据库-包裹跟踪器数据库
-
NoSQL 数据库,数据库
-
笔记本单元格(见单元格)
-
Notepad++,运行 Python 脚本
-
数值数据类型,数值类型-数值类型
-
NumFOCUS,标准库和包管理器
-
NumPy,为什么选择 Python 来做 Excel?
-
数组
-
广播,向量化和广播-向量化和广播
-
构造函数,有用的数组构造函数
-
数据类型,NumPy 数组-NumPy 数组
-
获取和设置元素,获取和设置数组元素-获取和设置数组元素
-
操作概述,NumPy 数组-NumPy 数组
-
通用函数(ufunc),通用函数(ufunc)-通用函数(ufunc)
-
向量化,向量化和广播-向量化和广播
-
视图与副本,视图 vs. 副本-视图 vs. 副本
-
-
数据分析问题,结论
-
目的,NumPy 基础
-
O
-
对象模型(Excel),Excel 对象模型-Excel 对象模型
-
对象关系映射器(ORM),数据库连接
-
对象
-
属性和方法,类和对象-类和对象
-
帮助文档,字符串
-
目的,属性和方法
-
-
作为类实例,日期时间类, 类和对象
-
类和,类和对象-类和对象
-
在集合中,Excel 对象模型
-
转换为布尔数据类型,read_excel 函数和 ExcelFile 类
-
帮助文档,字符串
-
初始化,类和对象
-
作为函数参数的可变对象,使用可变对象作为参数调用函数-默认参数为可变对象的函数
-
可变与不可变,可变 vs. 不可变 Python 对象-可变 vs. 不可变 Python 对象
-
目的,对象
-
变量,变量
-
-
ohlc 方法(pandas),重新取样
-
oletools,格式化 DataFrame 的数据部分
-
基于一的索引,Excel 对象模型
-
一维 NumPy 数组,NumPy 数组
-
一对多关系(数据库),包裹跟踪数据库
-
打开模式(VS Code),运行 Python 脚本
-
开源软件(OSS),标准库和包管理器
-
打开 Excel 实例,Excel 对象模型
-
OpenPyXL
-
编辑文件,使用 OpenPyXL 进行编辑-使用 OpenPyXL 进行编辑
-
格式化 DataFrame
-
数据部分,格式化 DataFrame 的数据部分
-
索引和标题,格式化 DataFrame 的索引和标题
-
-
并行化,并行读取工作表
-
读取文件,使用 OpenPyXL 进行读取-使用 OpenPyXL 进行写入,使用 OpenPyXL 进行读取
-
使用时机,何时使用哪个包
-
写入文件,使用 OpenPyXL 进行写入-使用 OpenPyXL 进行写入,使用 OpenPyXL 进行写入
-
XlsxWriter 对比,何时使用哪个包
-
-
运算符优先级,通过布尔索引进行选择
-
优化
-
读取/写入包,处理大型 Excel 文件-并行读取工作表
-
UDF(用户自定义函数)
-
缓存,缓存-缓存
-
减少跨应用调用(xlwings),减少跨应用调用-减少跨应用调用
-
原始值,使用原始值
-
-
xlwings 性能,提升性能-应用属性
-
-
可选参数(用于函数),定义函数
-
范围对象选项(xlwings),转换器和选项-转换器和选项
-
Oracle,数据库
-
ORM(对象关系映射器),数据库连接
-
OSS(开源软件),标准库和包管理器
-
外连接,连接和合并
-
输出
-
在 Jupyter 笔记本中,笔记本单元格
-
单独的层,关注点分离
-
P
-
包管理器,标准库和包管理器-标准库和包管理器,包管理器:Conda 和 pip-包管理器:Conda 和 pip
-
包
-
添加到 Python 包跟踪器,我们将要构建的内容-我们将要构建的内容
-
building,设置
-
安装,包管理器:Conda 和 pip,创建新的 Conda 环境
-
读者/写者包
-
excel.py 模块,excel.py 模块-excel.py 模块
-
列表,格式化 DataFrame 数据部分
-
OpenPyXL,OpenPyXL-使用 OpenPyXL 进行编辑
-
大文件优化,处理大型 Excel 文件-并行读取工作表
-
pyxlsb,pyxlsb-pyxlsb
-
何时使用,何时使用哪个包-何时使用哪个包
-
xlrd,使用 xlrd 进行阅读-使用 xlrd 进行阅读
-
XlsxWriter,XlsxWriter-XlsxWriter
-
xlutils,使用 xlutils 进行编辑
-
xlwt,使用 xlwt 进行写入-使用 xlutils 进行编辑
-
-
切换,excel.py 模块
-
版本,软件包管理器:Conda 和 pip-Conda 环境
-
-
pandas,为什么选择 Python 处理 Excel?
-
数据框
-
应用函数,应用函数-应用函数
-
算术运算,算术运算-算术运算
-
列,列-列
-
连接,连接-连接
-
复制,索引
-
创建,数据框和序列
-
描述性统计,描述性统计-描述性统计
-
重复数据,重复数据-重复数据
-
Excel 电子表格与,数据框和序列-数据框和序列
-
探索方法,导入 CSV 文件
-
导出,导入和导出数据框
-
Excel 中的格式化,在 Excel 中格式化数据框-格式化数据框的数据部分
-
在 Google 趋势案例研究中,处理数据框和动态数组-处理数据框和动态数组
-
分组数据,分组
-
导入,导入和导出数据框
-
索引,索引-索引
-
连接,连接和合并-连接和合并
-
限制,pandas 的限制
-
融合数据,数据透视和融合
-
合并,连接和合并-连接和合并
-
缺失数据,缺失数据-缺失数据
-
数据透视,数据透视和融合-数据透视和融合
-
绘图,绘图-Plotly
-
使用 xlwings 在 Excel 中读取/写入,处理数据框-处理数据框
-
数据选择,选择数据-使用 MultiIndex 进行选择
-
Series 与,DataFrame 和 Series-DataFrame 和 Series,按标签选择
-
设置数据,设置数据-通过添加新列设置数据
-
SQL 查询与,SQL 查询
-
文本列,处理文本列
-
转置,列
-
视图与副本,视图 vs. 复制
-
-
DatetimeIndex
-
创建,创建 DatetimeIndex-创建 DatetimeIndex
-
过滤,过滤 DatetimeIndex-过滤 DatetimeIndex
-
时区中的,处理时区-处理时区
-
-
Excel 文件
-
限制,使用 pandas 与 Excel 文件时的限制
-
阅读,read_excel 函数和 ExcelFile 类-read_excel 函数和 ExcelFile 类
-
写入,to_excel 方法和 ExcelWriter 类-to_excel 方法和 ExcelWriter 类
-
-
NumPy(见 NumPy)
-
绘图类型,Plotly
-
读取/写入包
-
excel.py 模块,excel.py 模块-excel.py 模块
-
列表,格式化 DataFrame 数据部分
-
OpenPyXL,OpenPyXL-使用 OpenPyXL 进行编辑
-
大文件优化,处理大型 Excel 文件-并行读取工作表
-
pyxlsb,pyxlsb-pyxlsb
-
何时使用,何时使用哪个包-何时使用哪个包
-
xlrd,使用 xlrd 进行读取-使用 xlrd 进行读取
-
XlsxWriter,XlsxWriter-XlsxWriter
-
xlutils,使用 xlutils 进行编辑
-
xlwt,使用 xlwt 进行写入-使用 xlutils 进行编辑
-
-
时间序列分析
-
相关性,重新基准化和相关性-重新基准化和相关性
-
百分比变化,移位和百分比变化-移位和百分比变化
-
重新基准化,重新基准化和相关性-重新基准化和相关性
-
重新采样,重新采样-重新采样
-
滚动窗口,滚动窗口
-
移位,移位和百分比变化-移位和百分比变化
-
-
-
并行化,并行读取工作表-并行读取工作表
-
父目录,切换到,Anaconda 命令提示符
-
字符串解析为日期时间对象,datetime 类
-
pass 语句,代码块和 pass 语句-代码块和 pass 语句
-
Path 类,案例研究:Excel 报表
-
pathlib 模块,案例研究:Excel 报表
-
pandas 的 pct_change 方法,移位和百分比变化
-
PEP 8 风格指南
-
示例,PEP 8:Python 代码风格指南-PEP 8:Python 代码风格指南
-
代码审查,PEP 8 和 VS Code
-
-
pep8_sample.py 示例,PEP 8:Python 代码风格指南-PEP 8:Python 代码风格指南
-
时间序列分析中的百分比变化,移位和百分比变化-移位和百分比变化
-
性能优化
-
读写包的读者/写者,处理大型 Excel 文件-并行读取工作表
-
UDFs
-
缓存,缓存-缓存
-
最小化跨应用程序调用(xlwings),最小化跨应用程序调用-最小化跨应用程序调用
-
原始值,使用原始值
-
-
在 xlwings 中,性能优化-应用程序属性
-
-
macOS 权限,将 Excel 用作数据查看器,Excel 对象模型
-
pictures (Excel), Matplotlib plots as, 图片:Matplotlib 绘图-图片:Matplotlib 绘图
-
Pillow, 图片:Matplotlib 绘图
-
pip, 标准库和包管理器-标准库和包管理器, 包管理器:Conda 和 pip-包管理器:Conda 和 pip
-
pivoting DataFrame data, 数据透视和 melting-数据透视和 melting
-
pivot_table function (pandas), 数据透视和 melting-数据透视和 melting
-
plot function (google_trends.py) example, 使用 UDFs 绘图-使用 UDFs 绘图
-
plot method (pandas), Matplotlib, 使用 UDFs 绘图-使用 UDFs 绘图
-
Plotly, Plotly-Plotly
-
heatmaps, 重基和相关性
-
installing, 包管理器:Conda 和 pip
-
-
plots
-
as Excel pictures, 图片:Matplotlib 绘图-图片:Matplotlib 绘图
-
list of plotting libraries, Plotly
-
in Matplotlib, Matplotlib-Matplotlib
-
in Plotly, Plotly-Plotly
-
types of (pandas), Plotly
-
-
plotting
-
DataFrame data
-
list of plotting libraries, Plotly
-
with Matplotlib, Matplotlib-Matplotlib
-
with Plotly, Plotly-Plotly
-
purpose of, 绘图
-
-
with UDFs, 使用 UDFs 绘图-使用 UDFs 绘图
-
-
pop method (lists), 列表
-
position (pandas)
-
selecting data by, 按位置选择-按位置选择
-
setting data by, 按标签或位置设置数据
-
-
positional arguments (for functions), 调用函数
-
POST requests, Web APIs
-
PostgreSQL、数据库、后端
-
Power BI、Power BI
-
Power Pivot、前言、Power Query 和 Power Pivot
-
Power Query、前言、Power Query 和 Power Pivot
-
《实用程序员》(Hunt 和 Thomas)、DRY 原则
-
表示层、关注点的分离
-
主键(数据库)、包裹跟踪器数据库
-
打印函数、运行 Python 脚本、调试 UDF
-
编程 Excel、读取/写入文件相对于、本书的组织结构
-
编程语言
-
最佳实践
-
DRY 原则、DRY 原则
-
关注点的分离、关注点的分离-关注点的分离
-
测试、测试
-
版本控制、版本控制-版本控制
-
-
Excel 作为、Excel 是一种编程语言-Excel 是一种编程语言
-
-
app 对象的属性(xlwings)、App 属性
-
伪随机数、生成、有用的数组构造器
-
PTVS(Visual Studio 的 Python 工具)、Visual Studio Code
-
拉取请求、版本控制
-
pwd 命令、Anaconda Prompt
-
PyArrow、与 pandas 的限制
-
pycache 文件夹、模块和 import 语句
-
PyCharm、运行 Python 脚本
-
PyDev、运行 Python 脚本
-
pyexcel、格式化数据帧的数据部分
-
PyExcelerate、格式化数据帧的数据部分
-
PyInstaller、Python 依赖
-
pylightxl、格式化数据帧的数据部分
-
PyPI(Python 包索引)、标准库和包管理器、我们将构建什么、Web API
-
PyPy、标准库和包管理器
-
PySpark、与 pandas 的限制
-
Python
-
Excel 的优势、用于 Excel 的 Python
-
跨平台兼容性,跨平台兼容性
-
现代语言特性,现代语言特性-现代语言特性
-
可读性,可读性和可维护性-可读性和可维护性
-
科学计算,科学计算
-
标准库和包管理器,标准库和包管理器-标准库和包管理器
-
-
Anaconda 发行版(参见 Anaconda)
-
布尔运算符,布尔值
-
类,属性和方法
-
注释,数学运算符
-
控制流
-
代码块,代码块和 pass 语句-代码块和 pass 语句
-
条件表达式,if 语句和条件表达式
-
字典推导式,列表、字典和集合推导式
-
for 循环,for 和 while 循环-for 和 while 循环
-
if 语句,if 语句和条件表达式-if 语句和条件表达式
-
列表推导式,列表、字典和集合推导式
-
pass 语句,代码块和 pass 语句-代码块和 pass 语句
-
集合推导式,列表、字典和集合推导式
-
while 循环,for 和 while 循环
-
-
数据结构
-
字典,字典-字典
-
列表,集合
-
列表,列表-列表
-
用途,数据结构
-
集合,集合-集合
-
元组,元组
-
-
数据类型
-
布尔值,布尔值-布尔值
-
可变与不可变,可变 vs. 不可变 Python 对象
-
数字,数值类型-数值类型
-
用途,数据类型
-
字符串, 字符串-字符串
-
-
函数
-
调用, 函数, 调用函数, 带可变对象作为参数调用函数-带可变对象作为默认参数的函数
-
定义, 定义函数
-
目的, 函数
-
-
历史背景, 为什么选择 Python 处理 Excel?-为什么选择 Python 处理 Excel?
-
索引, 索引
-
交互式会话
-
结束, Python REPL:交互式 Python 会话
-
启动, Python REPL:交互式 Python 会话-Python REPL:交互式 Python 会话
-
-
换行符, 列表
-
对数计算, 变化和百分比变化
-
macOS 权限, 使用 Excel 作为数据查看器
-
数学运算符, 数学运算符
-
模块搜索路径, RunPython 函数-RunPython 函数, 设置
-
模块
-
日期时间模块, datetime 类-datetime 类, 处理时区感知日期时间对象-处理时区感知日期时间对象
-
导入, 模块和 import 语句-模块和 import 语句
-
时区模块, 处理时区感知日期时间对象
-
-
对象
-
属性和方法, 属性和方法, 类和对象-类和对象
-
作为类实例, datetime 类
-
类与, 类和对象-类和对象
-
帮助文档, 字符串
-
初始化, 类和对象
-
可变函数参数, 带可变对象作为参数调用函数-带可变对象作为默认参数的函数
-
可变与不可变,可变与不可变的 Python 对象-可变与不可变的 Python 对象
-
用途,对象
-
-
包,构建,设置
-
PEP 8 样式指南
-
示例,PEP 8:Python 代码风格指南-PEP 8:Python 代码风格指南
-
代码检查,PEP 8 和 VS Code
-
-
用途,前言-前言
-
保存文件,UDF 快速入门
-
脚本(请参阅脚本)
-
切片,切片
-
变量,变量
-
版本,Python 和 Excel 版本
-
xlwings 依赖项,Python 依赖-Python 依赖
-
-
python 命令,Python REPL:交互式 Python 会话
-
Python 数据分析库(参见 pandas)
-
Python 交互窗口(VS Code),Python 脚本与代码单元
-
Python 解释器(xlwings),设置
-
Python 包追踪器案例研究
-
应用程序结构,应用程序结构
-
后端,后端-后端
-
调试,调试-调试
-
前端,前端-前端
-
-
数据库
-
连接,数据库连接-数据库连接
-
SQL 注入,SQL 注入-SQL 注入
-
SQL 查询,SQL 查询-SQL 查询
-
结构,包裹追踪器数据库-包裹追踪器数据库
-
类型,数据库-数据库
-
-
错误处理,异常-异常
-
包,添加,我们将构建什么-我们将构建什么
-
web API,Web API-Web API
-
-
Python REPL,Python REPL:交互式 Python 会话-Python REPL:交互式 Python 会话
-
Python Tools for Visual Studio (PTVS),Visual Studio Code
-
Python 风格,if 语句和条件表达式
-
PYTHONPATH 设置 (xlwings),RunPython 函数-RunPython 函数,设置,UDF 快速入门
-
pytrends,包管理器:Conda 和 pip,从 Google 趋势获取数据-从 Google 趋势获取数据
-
pyxlsb
-
安装,包管理器:Conda 和 pip
-
读取文件,read_excel 函数和 ExcelFile 类,pyxlsb-pyxlsb
-
使用时机,何时使用哪个包
-
Q
-
查询(SQL),SQL 查询-SQL 查询
-
快速启动命令 (xlwings),快速启动命令
-
导入 UDF,UDF 快速入门-UDF 快速入门
-
RunPython 函数和,RunPython 函数-RunPython 函数
-
-
退出命令,Python REPL:交互式 Python 会话
R
-
随机数生成,有用的数组构造器
-
范围函数,for 和 while 循环-for 和 while 循环
-
范围对象 (xlwings),Excel 对象模型-Excel 对象模型
-
转换器,转换器和选项
-
选项,转换器和选项-转换器和选项
-
-
原始字符串,导出 CSV 文件
-
原始值,原始值,使用原始值
-
read 函数 (excel.py),excel.py 模块
-
Python 可读性,可读性和可维护性-可读性和可维护性
-
读取器/写入器包
-
excel.py 模块,excel.py 模块-excel.py 模块
-
列表,格式化 DataFrame 的数据部分
-
OpenPyXL,OpenPyXL-使用 OpenPyXL 进行编辑
-
大文件优化,处理大型 Excel 文件-并行读取工作表
-
pyxlsb,pyxlsb-pyxlsb
-
何时使用,何时使用哪个包-何时使用哪个包
-
xlrd,使用 xlrd 进行读取-使用 xlrd 进行读取
-
XlsxWriter,XlsxWriter-XlsxWriter
-
xlutils,使用 xlutils 进行编辑
-
xlwt,使用 xlwt 进行写入-使用 xlutils 进行编辑
-
-
读取
-
在 Excel 中使用 xlwings 的数据框架,使用数据框架工作-使用数据框架工作
-
文件(Excel)
-
pandas 中的限制,使用 pandas 处理 Excel 文件的限制
-
使用 OpenPyXL,使用 OpenPyXL 进行读取-使用 OpenPyXL 进行写入,使用 OpenPyXL 进行读取
-
使用 pandas,read_excel 函数和 ExcelFile 类-read_excel 函数和 ExcelFile 类
-
并行处理,并行读取工作表-并行读取工作表
-
编程 Excel 与,本书的组织方式
-
使用 pyxlsb,pyxlsb-pyxlsb
-
从 URL 中,read_excel 函数和 ExcelFile 类
-
使用 xlrd,使用 xlrd 进行读取-使用 xlrd 进行读取,使用 xlrd 进行读取
-
-
-
read_csv 函数(pandas),导入 CSV 文件-导入 CSV 文件,创建 DatetimeIndex
-
read_excel 函数(pandas),DataFrame 和 Series,案例研究:Excel 报告,read_excel 函数和 ExcelFile 类-read_excel 函数和 ExcelFile 类,何时使用哪个包
-
时间序列分析中的重基准,重基准与相关性-重基准与相关性
-
重新计算函数(Excel),UDF 快速入门
-
记录(数据库),包裹跟踪器数据库
-
递归式全局搜索,案例研究:Excel 报告
-
Redis,数据库
-
区域设置(Excel),Excel 对象模型-Excel 对象模型
-
重新索引方法(pandas),索引
-
关系数据库,数据库-数据库
-
相对路径,Anaconda 提示符,后端
-
删除
-
重复数据(pandas),重复数据
-
缺失数据行(pandas),缺失数据
-
-
重命名
-
列(pandas),列
-
Jupyter 笔记本,运行 Jupyter 笔记本
-
-
重新排序列(pandas),列
-
替换方法(pandas),通过替换值设置数据
-
替换值(pandas),通过设置数据,通过替换值设置数据
-
报告案例研究(Excel),案例研究:Excel 报告-案例研究:Excel 报告,案例研究(重访):Excel 报告,案例研究(再重访):Excel 报告-案例研究(再重访):Excel 报告
-
表征状态传输(REST)API,Web API
-
Requests 包,Web API
-
函数必需的参数(参数),定义函数
-
重新采样方法(pandas),重新采样
-
时间序列分析中的重采样,重新采样-重新采样
-
重置
-
索引(pandas),索引
-
多索引(pandas),通过多索引选择
-
-
reset_index 方法(pandas),索引
-
reshape 函数(NumPy),有用的数组构造函数
-
解析方法(Path 类),案例研究:Excel 报告
-
REST(表征状态传输)API,Web API
-
重启按钮(VS Code 调试器),调试器
-
返回装饰器(xlwings),与数据框和动态数组一起工作,子装饰器
-
返回语句(用于函数),定义函数
-
revenues.py
示例,最小化跨应用调用 -
rglob 方法(Path 类),案例研究:Excel 报告
-
右连接,连接和合并
-
pandas 中的 rolling 方法,滚动窗口
-
时间序列分析中的滚动窗口,滚动窗口
-
运行文件按钮(VS Code),运行 Python 脚本
-
主运行按钮(xlwings),运行主程序-运行主程序
-
Jupyter 笔记本单元格的运行顺序,运行顺序重要
-
运行
-
Anaconda Prompt
-
在 macOS 中,Anaconda Prompt
-
在 Windows 中,Anaconda Prompt
-
-
文本编辑器中的代码,Visual Studio Code
-
Jupyter 笔记本,运行 Jupyter 笔记本-运行 Jupyter 笔记本, 运行 Jupyter 笔记本-运行 Jupyter 笔记本
-
宏(Excel),运行 Python 函数
-
脚本
-
导入模块与,后端
-
在 VS Code 中,运行 Python 脚本-运行 Python 脚本
-
-
xlwings 中的 VBA 代码,运行 VBA 代码-运行 VBA 代码
-
Anaconda Prompt 中的 VS Code,运行 Python 脚本
-
-
xlwings 中的 RunPython 函数,运行 Python 函数-运行 Python 无快速启动命令, 设置
S
-
sales_report_openpyxl.py
,案例研究(再访):Excel 报告 -
sales_report_pandas.py
示例,案例研究:Excel 报告-案例研究:Excel 报告 -
sales_report_xlsxwriter.py
,案例研究(再访):Excel 报告 -
sales_report_xlwings.py
示例,案例研究(再再访):Excel 报告 -
受限环境,Excel 对象模型, Excel 对象模型
-
保存
-
VS Code 中的 Jupyter 笔记本,运行 Jupyter 笔记本
-
Python 文件,UDF 快速入门
-
-
标量,向量化和广播
-
Python 中的科学计算,科学计算
-
脚本
-
Jupyter 笔记本单元格中,带代码单元格的 Python 脚本
-
命名空间,模块和导入语句
-
命名,模块和导入语句
-
运行
-
导入模块与,后端
-
在 VS Code 中,运行 Python 脚本-运行 Python 脚本
-
-
-
浏览命令历史记录,Anaconda 提示符
-
Seaborn,Plotly
-
选择
-
列(pandas),按标签选择
-
DataFrame 数据
-
通过布尔索引,按布尔索引选择-按布尔索引选择
-
通过标签,按标签选择
-
使用多索引,使用多索引进行选择-使用多索引进行选择
-
按位置,按位置选择-按位置选择
-
-
-
self 参数(类),类和对象
-
职责分离,职责分离-职责分离,应用程序结构
-
Series
-
算术操作,算术操作
-
DataFrames 与,DataFrame 和 Series-DataFrame 和 Series,按标签选择
-
描述性统计,描述性统计-描述性统计
-
-
集合理解,列表、字典和集合理解
-
集合构造函数,集合-集合
-
基于集合的语言,SQL 查询
-
集合,集合-集合
-
设置
-
数组元素(NumPy),获取和设置数组元素-获取和设置数组元素
-
DataFrame 数据
-
通过添加列,通过添加新列设置数据
-
通过布尔索引,通过布尔索引设置数据-通过布尔索引设置数据
-
按标签,按标签或位置设置数据
-
按位置,按标签或位置设置数据
-
通过替换值,通过替换值设置数据
-
-
索引 (pandas),索引
-
-
xlwings 部署设置,设置-设置
-
set_index 方法 (pandas),索引
-
浅复制,可变 vs 不可变的 Python 对象
-
sheet 对象 (xlwings),Excel 对象模型, Excel 对象模型
-
表格(参见文件)
-
shift 方法 (pandas),偏移和百分比变化
-
时间序列分析中的时间偏移,偏移和百分比变化-偏移和百分比变化
-
显示控制台复选框 (xlwings 插件),调试
-
关闭 Jupyter 笔记本,关闭 Jupyter 笔记本
-
重要的空白,代码块和 pass 语句
-
简单回报,偏移和百分比变化
-
单击 VS Code,运行 Python 脚本
-
切片
-
数组 (NumPy),获取和设置数组元素-获取和设置数组元素
-
带标签的 (pandas),按标签选择
-
在 Python 中,切片
-
范围对象 (xlwings),Excel 对象模型
-
-
排序
-
索引 (pandas),索引-索引
-
列表,列表
-
-
sort_index 方法 (pandas),索引-索引
-
源控制,版本控制-版本控制
-
意大利面代码,现代语言特性
-
电子表格 (Excel),DataFrame 与,DataFrame 和 Series-DataFrame 和 Series
-
Spyder,运行 Python 脚本
-
SQL 注入,SQL 注入-SQL 注入
-
SQL 查询,SQL 查询-SQL 查询
-
SQL Server,数据库
-
SQLAlchemy,数据库连接-数据库连接
-
强制外键,SQL 查询
-
SQL 注入,SQL 注入
-
-
SQLite,数据库-数据库
-
外键中,SQL 查询
-
替换为 PostgreSQL,后端
-
VS Code 扩展,包跟踪器数据库
-
-
独立工作簿(xlwings),独立工作簿:摆脱 xlwings 插件-独立工作簿:摆脱 xlwings 插件
-
标准库(Python),标准库和包管理器-标准库和包管理器
-
启动交互式 Python 会话,Python REPL:交互式 Python 会话-Python REPL:交互式 Python 会话
-
无状态资源,Web API
-
状态栏(VS Code),安装和配置
-
Step Into 按钮(VS Code 调试器),调试器
-
Step Out 按钮(VS Code 调试器),调试器
-
Step Over 按钮(VS Code 调试器),调试器
-
停止按钮(VS Code 调试器),调试器
-
存储 VBA 函数,运行 VBA 代码
-
strftime 方法,日期时间类
-
字符串,字符串-字符串
-
转换为日期时间数据类型,创建日期时间索引
-
DataFrames 中的格式化,应用函数-应用函数
-
DataFrames 中的方法,处理文本列
-
原始字符串,导出 CSV 文件
-
-
strip 方法(pandas),处理文本列
-
strptime 方法,日期时间类
-
Python 的风格指南(参见 PEP 8 风格指南)
-
DataFrames 的 style 属性,格式化 DataFrame 的数据部分
-
styleframe,格式化 DataFrame 的数据部分
-
sub 装饰器(xlwings),子装饰器-子装饰器
-
Sublime Text,运行 Python 脚本
-
sum 通用函数(NumPy),通用函数(ufunc)
-
切换读取器/写入器包,excel.py 模块
-
语法高亮,笔记本单元格,Visual Studio Code
T
-
表连接,数据库
-
Python Package Tracker 中的表格,前端
-
尾部方法(pandas),导入 CSV 文件
-
temperature.py 示例,模块和 import 语句
-
终端(macOS),Anaconda 提示符-Anaconda 提示符
- 禁用 Conda 环境的自动激活,禁用自动激活
-
三元运算符,if 语句和条件表达式
-
测试,测试
-
数据框中的文本列,处理文本列
-
文本编辑器
-
列表,运行 Python 脚本
-
VS Code(参见 VS Code)
-
-
文本函数(SQLAlchemy),SQL 注入
-
%%time 单元魔法,并行读取表格
-
时间序列分析
-
相关性,重新基准化和相关性-重新基准化和相关性
-
DatetimeIndex(pandas)
-
创建,创建 DatetimeIndex-创建 DatetimeIndex
-
过滤,过滤 DatetimeIndex-过滤 DatetimeIndex
-
中的时区,处理时区-处理时区
-
-
Excel 限制,使用 pandas 进行时间序列分析
-
百分比变化,平移和百分比变化-平移和百分比变化
-
重新基准化,重新基准化和相关性-重新基准化和相关性
-
重采样,重采样-重采样
-
滚动窗口,滚动窗口
-
平移,平移和百分比变化-平移和百分比变化
-
-
时间序列,用途,使用 pandas 进行时间序列分析
-
时区
-
在 DatetimeIndex(pandas)中,处理时区-处理时区
-
在 Python 中,使用时区感知的 datetime 对象-使用时区感知的 datetime 对象
-
-
%%timeit 单元魔法,并行读取工作表
-
时区模块,处理时区感知的 datetime 对象
-
标题,添加到数据帧中,在 Excel 中格式化数据帧
-
to_datetime 函数(pandas),创建 DatetimeIndex
-
to_excel 方法(pandas),to_excel 方法和 ExcelWriter 类-to_excel 方法和 ExcelWriter 类,何时使用哪个包
-
转置数据帧,列
-
布尔数据类型,布尔值-布尔值
-
try/except 语句,异常-异常
-
元组构造函数,集合
-
元组,元组
-
二维 NumPy 数组,NumPy 数组
-
类型注解,类型提示
-
类型提示,类型提示
-
tz_convert 方法(pandas),处理时区
-
tz_localize 方法(pandas),处理时区
U
-
UDFs(用户定义函数)
-
调试,调试 UDFs-调试 UDFs
-
Google 趋势案例研究
-
数据帧和动态数组,处理数据帧和动态数组-处理数据帧和动态数组
-
Google 趋势解释,介绍 Google 趋势-介绍 Google 趋势
-
获取数据,从 Google 趋势获取数据-从 Google 趋势获取数据
-
绘制数据,使用 UDFs 绘图-使用 UDFs 绘图
-
-
导入,UDF 快速入门-UDF 快速入门
-
性能优化
-
缓存,缓存-缓存
-
最小化跨应用程序调用(xlwings),最小化跨应用程序调用-最小化跨应用程序调用
-
原始值,使用原始值
-
-
要求,UDFs 入门
-
子装饰器,子装饰器-子装饰器
-
-
NumPy 中的通用函数(ufuncs),通用函数(ufunc)-通用函数(ufunc)
-
用户界面(参见前端)
-
UNIQUE 函数(Excel),使用数据框和动态数组
-
单元测试,测试
-
NumPy 中的通用函数,通用函数(ufunc)-通用函数(ufunc)
-
解包字典,字典
-
xlwings 中的 update 方法,图片:Matplotlib 图表
-
更新 xlwings,Excel 加载项
-
大小写转换,字符串
-
上采样,重采样
-
URL,从中读取文件,read_excel 函数和 ExcelFile 类
-
使用范围,使用 xlrd 读取
-
用户界面(见前端)
-
用户定义的函数(见 UDFs)
-
协调世界时(Coordinated Universal Time,UTC),处理时区,处理时区感知的 datetime 对象
V
-
Vaex,与 pandas 的限制
-
在 Python Package Tracker 中验证数据,前端
-
pandas 中的数值,通过替换设置数据,通过替换数值设置数据
-
变量资源管理器(Jupyter 笔记本),运行 Jupyter 笔记本
-
变量,变量
-
VBA(Visual Basic for Applications),前言
-
插件,DRY 原则
-
通过引用(ByRef)和传值(ByVal)函数参数,使用可变对象作为参数调用函数-使用可变对象作为参数调用函数
-
Case 语句,字典
-
代码块,代码块和 pass 语句
-
循环中的计数变量,for 和 while 循环
-
数据结构,数据结构
-
For Each 语句,for 和 while 循环
-
函数,DRY 原则,运行 VBA 代码
-
索引,索引
-
现代语言特性,现代语言特性-现代语言特性
-
可读性,可读性和可维护性
-
在 xlwings 中运行,运行 VBA 代码-运行 VBA 代码
-
RunPython 函数,RunPython 函数-无快速启动命令的 RunPython,设置
-
信任访问 VBA 项目模型设置,UDF 快速入门
-
变量,变量
-
With 语句,read_excel 函数和 ExcelFile 类
-
包装函数,UDF 快速入门
-
xlwings 范围对象与,Excel 对象模型-Excel 对象模型
-
-
VCS(版本控制系统),版本控制
-
向量化,向量化和广播-向量化和广播,使用 pandas 进行数据分析,算术运算
-
版本控制,版本控制-版本控制,Visual Studio Code
-
版本
-
包的版本,包管理器:Conda 和 pip-Conda 环境
-
Windows 上的确定,安装
-
-
视图函数(xlwings),使用 Excel 作为数据查看器-使用 Excel 作为数据查看器
-
查看
-
macOS 中的当前目录,Anaconda 提示符
-
文件扩展名,Anaconda 提示符-Anaconda 提示符
-
-
视图,副本与对比,视图 vs. 副本-视图 vs. 副本,视图 vs. 副本
-
Vim,运行 Python 脚本
-
虚拟环境,Conda 环境
-
VisiCalc,为什么选择 Python 处理 Excel?
-
Visual Basic for Applications(参见 VBA)
-
Visual Studio,Visual Studio Code
-
可变函数(Excel),UDF 快速入门
-
VS Code(Visual Studio Code)
-
优点,Visual Studio Code-Visual Studio Code
-
注释,数学运算符
-
组件,开发环境
-
配置,安装和配置-安装和配置
-
调试,调试, 调试 UDFs, 调试器-调试器
-
安装,安装和配置
-
Jupyter 笔记本
-
运行,运行 Jupyter 笔记本-运行 Jupyter 笔记本
-
保存,运行 Jupyter 笔记本
-
-
代码检查,PEP 8 和 VS Code
-
目的,开发环境
-
运行 Python 脚本,运行 Python 脚本-运行 Python 脚本
-
运行文件按钮,运行 Python 脚本
-
在 Anaconda Prompt 中运行,运行 Python 脚本
-
单击,在此处运行 Python 脚本
-
SQLite 扩展,包追踪器数据库
-
Visual Studio 与,Visual Studio Code
-
W
-
墙时间,并行读取工作表
-
web APIs,Web APIs-Web APIs
-
while 循环,for 和 while 循环
-
空格,代码块和 pass 语句
-
Windows
-
ActiveX 控件,RunPython 函数
-
Anaconda Prompt,运行,Anaconda Prompt
-
命令提示符,Anaconda Prompt
- 禁用自动激活 Conda 环境,禁用自动激活
-
当前目录,列出文件,Anaconda Prompt
-
提取宏代码(Xlsxwriter),XlsxWriter
-
文件扩展名,查看,Anaconda Prompt
-
文件路径作为原始字符串,导出 CSV 文件
-
表单控件(Excel),RunPython 函数
-
冻结可执行文件,Python 依赖
-
列分隔符(Excel),更改,Excel 对象模型
-
显示控制台复选框(xlwings 插件),调试
-
使用 UDF 服务器,设置
-
用户定义的函数(参见 UDFs)
-
VS Code
-
配置,安装和配置
-
安装,安装和配置
-
-
xlwings 依赖,xlwings 基础
-
僵尸进程,xlwings 基础
-
-
Wing Python IDE,运行 Python 脚本
-
WinPython,Python 依赖
-
with 语句,read_excel 函数和 ExcelFile 类
-
VBA 函数包装器,UDF 快速入门
-
写函数(excel.py),excel.py 模块
-
写入包(参见读取器/写入器包)
-
写入
-
使用 xlwings 将 DataFrames 导出到 Excel,处理 DataFrames-处理 DataFrames
-
文件(Excel)
-
pandas 的限制,使用 pandas 处理 Excel 文件时的限制
-
使用 OpenPyXL,使用 OpenPyXL 进行写入-使用 OpenPyXL 进行写入,使用 OpenPyXL 进行写入
-
使用 pandas,to_excel 方法和 ExcelWriter 类-to_excel 方法和 ExcelWriter 类
-
编程 Excel 对比,本书的组织结构
-
使用 XlsxWriter,XlsxWriter-XlsxWriter,使用 XlsxWriter 进行写入
-
使用 xlwt,使用 xlwt 进行写入-使用 xlutils 进行编辑
-
-
X
-
xlrd
-
并行化,并行读取工作表
-
读取文件,使用 xlrd 进行读取-使用 xlrd 进行读取,使用 xlrd 进行读取
-
何时使用,何时使用哪个包
-
-
xls 文件,读取/写入/编辑,xlrd, xlwt 和 xlutils
-
xlsb 文件,读取,read_excel 函数和 ExcelFile 类,pyxlsb-pyxlsb
-
xlsm 文件
-
启用宏,RunPython 函数
-
提取宏代码(Xlsxwriter),XlsxWriter-XlsxWriter
-
-
XlsxWriter
-
格式化 DataFrames
-
数据部分,格式化 DataFrame 的数据部分
-
索引和标题,格式化 DataFrame 的索引和标题
-
-
OpenPyXL 对比,何时使用哪个包
-
何时使用,何时使用哪个包
-
写入文件,XlsxWriter-XlsxWriter, 使用 XlsxWriter 进行写入
-
-
xltrail,版本控制
-
xlutils
-
编辑文件,使用 xlutils 进行编辑
-
安装,包管理器:Conda 和 pip, xlrd, xlwt 和 xlutils
-
何时使用,何时使用哪个包
-
-
xlwings
-
图表(Excel),创建,Excel 图表-Excel 图表
-
转换器,转换器和选项
-
DataFrames,在 Excel 中读取/写入,使用 DataFrames-使用 DataFrames
-
定义名称(Excel),创建,定义名称-定义名称
-
依赖项,xlwings 基础
-
部署
-
配置层次结构,配置层次结构
-
Python 依赖项,Python 依赖-Python 依赖
-
设置,设置-设置
-
独立工作簿,独立工作簿:摆脱 xlwings 附加组件-独立工作簿:摆脱 xlwings 附加组件
-
-
作为数据查看器的 Excel,将 Excel 用作数据查看器-将 Excel 用作数据查看器
-
Excel 作为前端
-
安装 xlwings 附加组件,Excel 附加组件-Excel 附加组件
-
目的,使用 xlwings 将 Excel 用作前端
-
快速启动命令,快速启动命令
-
运行主按钮,运行主按钮-运行主按钮
-
RunPython 函数,RunPython 函数-不使用快速启动命令的 RunPython
-
-
Excel 依赖项,何时使用哪个包
-
Excel 对象模型和,Excel 对象模型-Excel 对象模型
-
macOS 权限,将 Excel 用作数据查看器
-
解决缺失功能的方法,如何解决缺失功能-如何解决缺失功能
-
性能优化,性能优化-应用程序属性
-
图片(Excel),Matplotlib 绘图,图片:Matplotlib 绘图-图片:Matplotlib 绘图
-
目的,Excel 自动化-开始使用 xlwings
-
Python 包跟踪器(参见 Python 包跟踪器)
-
范围对象选项,转换器和选项-转换器和选项
-
显示控制台复选框,调试
-
更新中,Excel 插件
-
用户定义函数(参见 UDFs)
-
运行 VBA 代码,运行 VBA 代码-运行 VBA 代码
-
-
xlwings CLI
-
添加插件安装命令,Excel 插件
-
目的,Excel 插件
-
快速启动命令,快速启动命令
-
-
xlwings PRO,设置
-
xlwt
-
何时使用,选择哪个包
-
写入文件,使用 xlwt 进行写入-使用 xlutils 进行编辑
-
Z
-
Python 之禅,可读性和可维护性
-
从零开始的索引,Excel 对象模型
-
用零替换 NaN 值,算术运算
-
僵尸进程,xlwings 基础
关于作者
Felix Zumstein 是 xlwings 的创始人和维护者,这是一个流行的开源包,允许在 Windows 和 macOS 上使用 Python 自动化 Excel。他还组织了伦敦和纽约的 xlwings 社区聚会,促进了多种创新解决方案在 Excel 中的应用。
作为 Excel 文件版本控制系统 xltrail 的 CEO,他与数百名将 Excel 用于业务关键任务的用户进行过交流,因此深入了解了各个行业中 Excel 的典型用途和问题。
后记
《Python for Excel》封面上的动物是虚假珊瑚蛇(Anilius scytale)。这种色彩鲜艳的蛇,也被称为美洲管蛇,分布在南美的圭亚那地区、亚马逊雨林和特立尼达和多巴哥。
假珊瑚蛇长约 70 厘米,具有鲜红色和黑色条纹。它的带状外观类似于珊瑚蛇,因此得名;然而,假珊瑚蛇缺少“真正”珊瑚蛇特有的黄色条纹。其身体大部分长度的直径大致相同,尾部非常短,使其看起来像管状。它的小眼睛被大头鳞覆盖。
这种穴居蛇已被观察到是卵胎生的。它以甲虫、两栖动物、蜥蜴、鱼类和其他小蛇为食。假珊瑚蛇还保留了距肛门附近的小骨突,这些是臀部和偶尔上肢骨的遗传痕迹。这个特征以及它的厚骨骼和颅骨独特的形状使假珊瑚蛇与蛇的祖先,类似蜥蜴的状态密切相关。
假珊瑚蛇的保护状态为“数据不足”,意味着尚无足够信息来评估其灭绝的威胁。O’Reilly 封面上的许多动物都濒危;它们对世界至关重要。
封面插图由 Karen Montgomery 创作,基于英国百科全书自然历史的黑白雕刻。封面字体为 Gilroy Semibold 和 Guardian Sans。正文字体为 Adobe Minion Pro;标题字体为 Adobe Myriad Condensed;代码字体为 Dalton Maag 的 Ubuntu Mono。
-
前言
-
为什么写这本书
-
这本书适合谁
-
本书组织结构
-
Python 和 Excel 版本
-
本书使用的约定
-
使用代码示例
-
O’Reilly 在线学习
-
如何联系我们
-
致谢
-
-
I. Python 简介
-
1. 为什么选择 Python 处理 Excel?
-
Excel 是一种编程语言
-
Excel 在新闻中
-
编程最佳实践
-
现代 Excel
-
-
Python 处理 Excel
-
可读性和可维护性
-
标准库和包管理器
-
科学计算
-
现代语言特性
-
跨平台兼容性
-
-
结论
-
-
2. 开发环境
-
Anaconda Python 发行版
-
安装
-
Anaconda Prompt
-
Python REPL:交互式 Python 会话
-
包管理器:Conda 和 pip
-
Conda 环境
-
-
Jupyter 笔记本
-
运行 Jupyter 笔记本
-
笔记本单元格
-
编辑模式与命令模式
-
运行顺序很重要
-
关闭 Jupyter 笔记本
-
-
Visual Studio Code
-
安装和配置
-
运行 Python 脚本
-
-
结束语
-
-
3. Python 入门
-
数据类型
-
对象
-
数值类型
-
布尔值
-
字符串
-
-
索引和切片
-
索引
-
切片
-
-
数据结构
-
列表
-
字典
-
元组
-
集合
-
-
控制流
-
代码块和
pass
语句 -
if
语句和条件表达式 -
for
和while
循环 -
列表、字典和集合推导
-
-
代码组织
-
函数
-
模块和
import
语句 -
datetime
类
-
-
PEP 8:Python 代码风格指南
-
PEP 8 和 VS Code
-
类型提示
-
-
结束语
-
-
II. pandas 简介
-
4. NumPy 基础
-
NumPy 入门
-
NumPy 数组
-
向量化和广播
-
通用函数(
ufunc
)
-
-
创建和操作数组
-
获取和设置数组元素
-
有用的数组构造器
-
视图 vs. 复制
-
-
结论
-
-
5. 使用 pandas 进行数据分析
-
DataFrame 和 Series
-
索引
-
列操作
-
-
数据操作
-
选择数据
-
设置数据
-
缺失数据
-
重复数据
-
算术操作
-
处理文本列
-
应用函数
-
视图 vs. 复制
-
-
合并数据框
-
连接操作
-
连接和合并
-
-
描述性统计和数据聚合
-
描述性统计
-
分组
-
透视表和融合
-
-
绘图
-
Matplotlib
-
Plotly
-
-
导入和导出数据框
-
导出 CSV 文件
-
导入 CSV 文件
-
-
结论
-
-
6. 使用 pandas 进行时间序列分析
-
日期时间索引
-
创建日期时间索引
-
过滤日期时间索引
-
处理时区
-
-
常见时间序列操作
-
移动和百分比变化
-
重新基准化和相关性
-
重采样
-
滚动窗口
-
-
pandas 的限制
-
结论
-
-
III. 无 Excel 读写 Excel 文件
-
7. 使用 pandas 进行 Excel 文件操作
-
案例研究:Excel 报告
-
使用 pandas 读写 Excel 文件
-
read_excel 函数和 ExcelFile 类
-
to_excel 方法和 ExcelWriter 类
-
-
使用 pandas 与 Excel 文件时的限制
-
结论
-
-
8. 使用读写包操作 Excel 文件
-
读写包
-
何时使用哪个包
-
excel.py 模块
-
OpenPyXL
-
XlsxWriter
-
pyxlsb
-
xlrd, xlwt 和 xlutils
-
-
高级读写主题
-
处理大型 Excel 文件
-
在 Excel 中格式化数据框
-
案例研究(再次审视):Excel 报告
-
-
结论
-
-
IV. 使用 xlwings 编程 Excel 应用
-
9. Excel 自动化
-
开始使用 xlwings
-
将 Excel 用作数据查看器
-
Excel 对象模型
-
运行 VBA 代码
-
-
转换器、选项和集合
-
处理数据框
-
转换器和选项
-
图表、图片和定义名称
-
案例研究(再次审视):Excel 报告
-
-
高级 xlwings 主题
-
xlwings 基础
-
提高性能
-
如何处理缺失功能
-
-
结论
-
-
10. 使用 Python 扩展 Excel 工具
-
使用 xlwings 将 Excel 用作前端
-
Excel 加载项
-
快速启动命令
-
运行主程序
-
RunPython 函数
-
-
部署
-
Python 依赖
-
独立工作簿:摆脱 xlwings 加载项
-
配置层次结构
-
设置
-
-
结论
-
-
11. Python 包跟踪器
-
我们将构建什么
-
核心功能
-
Web API
-
数据库
-
异常
-
-
应用程序结构
-
前端
-
后端
-
调试
-
-
结论
-
-
12. 用户定义函数 (UDFs)
-
UDF 入门
- UDF 快速入门
-
案例研究:Google 趋势
-
介绍 Google 趋势
-
处理数据框和动态数组
-
从 Google 趋势获取数据
-
使用 UDF 绘图
-
调试 UDF
-
-
高级 UDF 主题
-
基本性能优化
-
缓存
-
子装饰器
-
-
结论
-
-
A. Conda 环境
-
创建新的 Conda 环境
-
禁用自动激活
-
-
B. 高级 VS Code 功能
-
调试器
-
VS Code 中的 Jupyter 笔记本
-
运行 Jupyter 笔记本
-
带有代码单元的 Python 脚本
-
-
-
C. 高级 Python 概念
-
类和对象
-
使用时区感知的 datetime 对象
-
可变 vs. 不可变 Python 对象
-
调用具有可变对象作为参数的函数
-
具有可变对象作为默认参数的函数
-
-
-
索引
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!