Python-Excel-指南-全-

Python Excel 指南(全)

原文:zh.annas-archive.org/md5/4cbcfc6fe67ce954227c7b15eef2b4c0

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

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//python-for-excel。

如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至 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

在第一个版本中,视觉缩进与代码逻辑对齐。这使得代码易于阅读和理解,从而更容易发现错误。在第二个版本中,对于第一次浏览代码的新开发人员可能看不到ElseIfElse条件——如果代码是大型代码库的一部分,这显然更为真实。

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 使我们能够以非常简洁的方式制定数学问题。例如,让我们来看看根据现代投资组合理论计算投资组合方差的一个更著名的金融公式:

投资组合方差用符号表示为,其中是各个资产的权重向量,是投资组合的协方差矩阵。如果wC是 Excel 范围,则可以在 VBA 中如下计算投资组合方差:

方差``=``Application``.``MMult``(``Application``.``MMult``(``Application``.``Transpose``(``w``),``C``),``w``)

比较这一点与 Python 中的几乎数学符号表示,假设wC是 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 错误处理涉及在示例中使用FinallyErrorHandler这样的标签。您通过GoToResume语句指示代码跳转到这些标签。早期,标签被认为是许多程序员所说的意大利面代码的原因:这是一种表达方式,意味着代码的流程难以跟踪,因此难以维护。这就是为什么几乎所有积极开发的语言都引入了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 键自动完成之前,开始键入前一个命令 (dirls -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 [ ]下打印它。但是,当你使用print函数或遇到异常时,它会直接在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语句以及forwhile循环,然后介绍允许你组织和结构化代码的函数和模块。最后,我会展示如何正确格式化你的 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 是区分大小写的,所以aA是两个不同的变量。变量名必须遵循某些规则:

  • 它们必须以字母或下划线开头

  • 它们必须由字母、数字和下划线组成

在这个关于变量的简短介绍之后,让我们看看如何进行函数调用!

函数

我将在本章后面更详细地介绍函数。现在,你只需要知道如何调用内置函数,比如我们在前面代码示例中使用的print。要调用函数,你需要在函数名后加上括号,并在括号内提供参数,这几乎相当于数学表示法:

function_name``(``argument1``,``argument2``,``...``)

现在让我们看看在对象的上下文中变量和函数是如何工作的!

属性和方法

在对象的上下文中,变量被称为属性,函数被称为方法:属性允许你访问对象的数据,而方法允许你执行操作。要访问属性和方法,你可以使用点号表示法,如myobject.attributemyobject.method()

让我们具体化一下:如果你编写了一个汽车赛车游戏,你很可能会使用一个表示汽车的对象。car对象可能有一个speed属性,通过car.speed可以获取当前速度,并且你可以通过调用car.accelerate(10)方法来加速汽车,这将使速度增加十英里每小时。

对象的类型及其行为由类定义,因此前面的例子需要你编写一个Car类。从Car类获取一个car对象的过程称为实例化,你可以通过调用类来实例化对象,就像调用函数一样:car = Car()。我们不会在本书中编写自己的类,但如果你对其工作原理感兴趣,请查看附录 C。

在接下来的一节中,我们将使用第一个对象方法使文本字符串变为大写,并且当我们讨论本章末尾关于datetime对象时,我们将回到对象和类的主题。现在,让我们继续讨论具有数值数据类型的对象!

数值类型

数据类型intfloat分别表示整数和浮点数。要找出给定对象的数据类型,使用内置的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 还有一些数值类型,本书不会使用或讨论:有 decimalfractioncomplex 数据类型。如果浮点数不准确是个问题(参见侧边栏),可以使用 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 中的布尔类型为 TrueFalse,与 VBA 完全相同。然而,布尔运算符 andornot 全部小写,而 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 对象都会评估为 TrueFalse。大多数对象为 True,但有一些会评估为 False,包括 None(见侧边栏),False0 或空数据类型,例如空字符串(我将在下一节介绍字符串)。

NONE

None 是一个内置常量,代表“没有值”,根据官方文档。例如,如果函数没有显式返回任何内容,它将返回 None。它还是在 Excel 中表示空单元格的良好选择,我们将在 第 III 部分 和 第 IV 部分 中看到。

若要双重检查对象是否为 TrueFalse,请使用 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索引不包含在内。如果省略startstop参数,它将分别包括从序列开始到结尾的所有内容。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']

要删除一个元素,可以使用 popdel。虽然 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']

请注意,你也可以在字符串中使用 lenin

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语句以及forwhile循环。if语句允许你仅在满足条件时执行某些代码行,而forwhile循环将重复执行一段代码。在本节末尾,我还将介绍列表推导式,这是构建列表的一种方式,可以作为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

如果你想做和我们在第一章里做的一样,即缩进elifelse语句,你会得到SyntaxError。Python 不允许你的代码缩进与逻辑不同。与 VBA 不同的是,Python 的关键词是小写的,而不是 VBA 中的ElseIf,Python 使用elifif语句是判断一个程序员是否新手或者已经采用 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语句和条件表达式,让我们在下一节转向forwhile循环。

forwhile循环

如果你需要重复做一些像打印十个不同变量的值这样的事情,最好不要把打印语句复制/粘贴十次。而是使用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 循环中一个计数变量,rangeenumerate 内置函数可以帮助你。让我们先看看 range,它提供一系列数字:你可以通过提供单个 stop 参数或提供 startstop 参数(带有可选的 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_celsiustemperature.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``dtTEMPERATURE_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")`![](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/py-xcl/img/00025.jpg)`non_celsius_scales=TEMPERATURE_SCALES[:``-``1``]![](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/py-xcl/img/00034.jpg)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 中,你经常会看到每个变量都用数据类型的缩写作为前缀,比如strEmployeeNamewbWorkbookName。虽然在 Python 中没有人会阻止你这样做,但这并不常见。你也不会找到类似于 VBA 的Option ExplicitDim语句来声明变量的类型。相反,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.onesnp.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 的一种方法是提供数据作为嵌套列表,并为columnsindex提供值:

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 子集的唯一方式。另一种重要的方式是使用布尔索引;让我们看看它是如何工作的!

按布尔索引选择

布尔索引是指使用仅包含 TrueFalse 的 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 &#124;
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 的索引设置为 continentcountry 的组合,你可以轻松选择特定大陆的所有行:

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 数据的最简单方法是使用lociloc属性为特定元素分配值。这是本节的起点,在转向操作现有 DataFrame 的其他方法之前:替换值和添加新列。

通过标签或位置设置数据

正如本章前面所指出的,当你调用df.reset_index()等数据框方法时,该方法总是应用于一个副本,保持原始数据框不变。然而,通过lociloc属性赋值会改变原始数据框。由于我想保持我们的数据框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 表示缺失数据,显示为 NaNNaN 是浮点数的标准表示为“非数字”。对于时间戳,使用 pd.NaT,对于文本,pandas 使用 None。使用 Nonenp.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 数组返回一个视图。但是对于数据框而言,情况更加复杂:lociloc 是否返回视图或副本往往难以预测,这使得它成为比较令人困惑的话题之一。因为改变视图和数据框副本是很大的区别,当 pandas 认为你以不合适的方式设置数据时,它经常会提出如下警告:SettingWithCopyWarning。为了避免这种颇为神秘的警告,这里有一些建议:

  • 在原始数据框上设置值,而不是在从另一个数据框切片得到的数据框上设置值

  • 如果你想要在切片后得到一个独立的数据框,那么要显式地复制:

  • 选择``=``df``.``loc``[:,``[``"国家"``,``"大陆"``]]``.``copy``()

虽然在处理 lociloc 时情况复杂,但值得记住的是,所有数据框方法如 df.dropna()df.sort_values("列名") 总是返回一个副本。

到目前为止,我们大多数时间都是在处理一个数据框。接下来的章节将展示多种将多个数据框合并为一个的方法,这是 pandas 提供的一个非常常见的强大工具。

合并数据框

在 Excel 中组合不同的数据集可能是一个繁琐的任务,通常涉及大量的VLOOKUP公式。幸运的是,pandas 的合并 DataFrame 功能是其杀手级功能之一,其数据对齐能力将极大地简化你的生活,从而大大减少引入错误的可能性。合并和连接 DataFrame 可以通过各种方式进行;本节只讨论使用concatjoinmerge的最常见情况。虽然它们有重叠之处,但每个函数都使特定任务变得非常简单。我将从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``,``...``])

另一方面,joinmerge 仅适用于两个 DataFrame,下面我们将看到。

连接与合并

当你连接两个 DataFrame 时,你将每个 DataFrame 的列合并到一个新的 DataFrame 中,同时根据集合理论决定行的处理方式。如果你之前有过与关系数据库的工作经验,那么这与 SQL 查询中的 JOIN 子句是相同的概念。图 5-3 显示了内连接、左连接、右连接和外连接四种连接类型如何通过使用两个示例 DataFrame df1df2 进行操作。

图 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而不是joinmerge接受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

由于joinmerge接受许多可选参数来适应更复杂的场景,我建议你查看官方文档以了解更多信息。

你现在知道如何操作一个或多个 DataFrame,这将引导我们数据分析旅程的下一步:理解数据。

描述性统计和数据聚合

在理解大数据集的一种方法是计算描述统计,如总和或平均值,可以针对整个数据集或有意义的子集。本节首先介绍了在 pandas 中如何进行这种操作,然后介绍了两种数据聚合到子集的方式:groupby方法和pivot_table函数。

描述性统计

描述性统计允许你通过使用定量的方法对数据集进行总结。例如,数据点的数量是一个简单的描述性统计量。像均值、中位数或众数这样的平均数也是其他流行的例子。DataFrame 和 Series 允许你通过诸如summeancount之类的方法方便地访问描述性统计信息。在本书中你将会遇到许多这样的方法,并且完整的列表可以通过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函数的第一个参数提供。indexcolumns定义了 DataFrame 的哪一列将成为数据透视表的行和列标签。values将根据aggfunc聚合到生成的 DataFrame 的数据部分中,aggfunc是一个可以作为字符串或 NumPy ufunc 提供的函数。最后,margins对应于 Excel 中的Grand Total,即如果不指定marginsmargins_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。从这个意义上说,meltpivot_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_sqlpd.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 的摘要信息。接下来,您可能希望使用headtail方法查看 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。如果你需要更改另一个数据类型(比如你想要Volumefloat而不是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,你也可以使用日期的部分,比如yearmonthday等。要访问具有数据类型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.expressas`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上有效的方法,如summean。还有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,包括countsummedianminmaxstd(标准差)或var(方差)。

到目前为止,我们已经看到了 pandas 最重要的功能。同样重要的是要理解 pandas 的局限性,即使它们可能现在仍然很远。

pandas 的局限性

当你的 DataFrame 开始变得更大时,了解 DataFrame 能容纳的上限是个好主意。与 Excel 不同,Excel 的每个工作表都有大约一百万行和一万二千列的硬性限制,而 pandas 只有软性限制:所有数据必须适合计算机可用的内存。如果情况不是这样,可能有一些简单的解决方案:只加载你需要的数据集中的那些列,或者删除中间结果以释放一些内存。如果这些方法都不起作用,有一些项目可能会让 pandas 用户感到熟悉,但可以处理大数据。其中一个项目是Dask,它在 NumPy 和 pandas 之上工作,允许你通过将数据拆分成多个 pandas DataFrame,并将工作负载分配到多个 CPU 核心或机器上来处理大型数据集。其他与某种 DataFrame 类似的大数据项目包括ModinKoalasVaexPySparkcuDFIbisPyArrow。我们将在下一章简要介绍 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_excelto_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_nameskiprowsusecols 参数,我们可以告诉 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_valueskeep_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语句的对象称为上下文管理器;这包括本章中的ExcelFileExcelWriter对象,以及我们将在第十一章中查看的数据库连接对象。

让我们看看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):空单元格、#NANAnull#N/AN/ANaNn/a-NaN1.#INDnan#N/A N/A-1.#QNAN-nanNULL-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.nannp.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 要写入的工作表名称。
startrowstartcol startrow 是 DataFrame 将写入的第一行,startcol 是第一列。这使用从零开始的索引,因此如果要将 DataFrame 写入到 B3 单元格,使用 startrow=2startcol=1
indexheader 如果要隐藏索引和/或标题,将它们分别设置为 index=Falseheader=False
na_repinf_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_excelto_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_excelto_excel 函数的 engine 参数中指定,或者在 ExcelFileExcelWriter 类中分别指定。引擎是包名的小写形式,因此要使用 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_celllast_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
浮点数 floatint

现在我们装备好使用 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_formatsheet["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.3333sheet["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的字符串来设置颜色。该值由三个十六进制值(FF0000)组成,分别对应所需颜色的红/绿/蓝值。十六进制表示十六进制数,使用十六进制基数而不是我们标准十进制系统使用的十进制基数。

查找颜色的十六进制值

要在 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.nrowssheet.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}"foriinrange(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代替applicationbook代替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.MySumSheet1.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 参数提供。默认情况下,它期望您的数据具有标题和索引,但您可以再次使用 indexheader 参数进行更改。而不是使用转换器,您还可以首先将值读取为嵌套列表,然后手动构建数据框,但使用转换器可以更轻松地处理索引和标题。

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 示例中使用了 indexheader 选项,但还有更多的选项可用,如 表 9-5 所示。

表 9-5. 内置选项

选项 描述
empty 默认情况下,空单元格被读取为  None。通过为 empty 提供值来更改这一点。
date 接受应用于日期格式单元格值的函数。
number 接受应用于数字的函数。
ndim 维度数:在读取时,使用 ndim 强制将范围的值按特定维度到达。必须是 None12。在读取值作为列表或 NumPy 数组时可用。
transpose 转置值,即将列转换为行或反之。
index 用于 pandas 的 DataFrame 和 Series:在读取时,用于定义 Excel 范围是否包含索引。可以是True/False或整数。整数定义将多少列转换为MultiIndex。例如,2将使用最左边的两列作为索引。在写入时,可以通过将index设置为TrueFalse来决定是否写出索引。
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 始终是一维的。这里还有一个例子,展示了emptydatenumber选项的工作方式:

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]

到目前为止,我们已经使用了booksheetrange对象。现在让我们继续学习如何处理从sheet对象访问的图表等集合!

图表、图片和定义名称

在本节中,我将向您展示如何处理通过sheetbook对象访问的三个集合:图表、图片和定义名称。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"`风格%matplotlibinlineimportmatplotlib.pyplotaspltplt.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 pillowpip install pillow来安装它。请注意,pictures.add还可以接受磁盘上图片的路径,而不是 Matplotlib 图表。

图表和图片是通过sheet对象访问的集合。下面我们将看看如何访问定义名称集合,可以从sheetbook对象中访问。让我们看看这样做有什么区别!

定义名称

在 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 如何允许您与其组件如 sheetrange 对象交互。掌握了这些知识,我们回到了第七章 的报告案例研究,并使用 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 PathConda 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.funcdef``hello``(``name``):``return``f``"Hello {name}!"

if __name__ ==``"__main__"``:xw``.``Book``(``"first_project.xlsm"``)``.``set_mock_caller``()``main``()

xw.Book.caller() 是一个 xlwings book 对象,它引用的是在点击“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:

  1. 首先确保将工作簿另存为带有 xlsm 或 xlsb 扩展名的宏启用工作簿。

  2. 添加 VBA 模块;要添加 VBA 模块,请通过 Alt+F11(Windows)或 Option-F11(macOS)打开 VBA 编辑器,并确保在左侧树视图中选择工作簿的 VBAProject,然后右键单击它,选择“插入” > “模块”,如图 10-5 所示。这将插入一个空的 VBA 模块,您可以在其中编写带有RunPython调用的 VBA 宏。

  3. 图 10-5. 添加一个 VBA 模块

  4. 添加对 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 中使用小写的truefalse,而 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 ServerOraclePostgreSQLMySQL。作为 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_idversion_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_id1的行。为了帮助您基于我们在第五章中学到的内容理解此查询,如果packagespackage_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> ----> 1print_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/numberExceptionase:#"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块始终运行时运行,无论是否引发错误。通常情况下,您只需使用tryexcept块。让我们看看给定不同输入时函数的输出:

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``()``.``parentdb_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_sqlread_sql方法运行 SQL 语句,这样您可以了解这两种方法的使用感觉。

迁移到 POSTGRESQL

如果您想用基于服务器的数据库 POSTGRESQL 替换 SQLite,您只需做几件事情。首先,您需要运行conda install psycopg2(或者如果您不使用 Anaconda 发行版,则运行pip install psycopg2-binary)来安装 POSTGRESQL 驱动程序。然后,在 database.py 中,根据 Table 11-3 中显示的 POSTGRESQL 版本更改create_engine函数中的连接字符串。最后,要创建表,您需要将packages.package_idINTEGER数据类型更改为 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 函数,与像SUMAVERAGE等内置函数的使用方式相同。与上一章类似,我们将从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``()

选项indexheader不需要,因为它们使用了默认参数,但我包含它们是为了向你展示它们在 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``()

我省略了indexheader参数,因为它们使用默认值,但随时可以保留它们。

添加参数 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.argxw.ret 装饰器之上;注意 xw.argxw.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 个)"``![](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/py-xcl/img/00067.jpg)start_date=start_date.date().isoformat()![](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/py-xcl/img/00058.jpg)`end_date=end_date.date().isoformat()# 进行 Google Trends 请求并返回 DataFrametrend=TrendReq(timeout=10)![](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/py-xcl/img/00082.jpg)trend.build_payload(kw_list=mids,timeframe=f"{start_date} {end_date}")![](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/py-xcl/img/00076.jpg)df=trend.interest_over_time()`![](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/py-xcl/img/00007.jpg)`# 用人类可读词替换 Google 的"mid"mids={"/m/05z1_":"Python","/m/02p97":"JavaScript","/m/0jgqg":"C++","/m/07sbkfb":"Java","/m/060kv":"PHP"}df=df.rename(columns=mids)![](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/py-xcl/img/00015.jpg)# 删除 isPartial 列returndf.drop(columns="isPartial"``)`

默认情况下,当您在函数向导中打开 Excel 时,它会调用该函数。由于这可能会使其变慢,特别是涉及 API 请求时,我们将其关闭。

可选地,为函数参数添加文档字符串,在您编辑相应参数时将显示在函数向导中,如图 12-8。

函数的文档字符串显示在函数向导中,如图 12-8。

assert语句是在用户提供了太多mids时引发错误的简便方式。Google Trends 每次查询最多允许五个mids

pytrends 期望的开始和结束日期是一个形如YYYY-MM-DD YYYY-MM-DD的字符串。因为我们提供的是日期格式的单元格作为开始和结束日期,它们将作为datetime对象到达。在它们上调用dateisoformat方法将正确地格式化它们。

我们正在实例化一个 pytrends 的request对象。通过将timeout设置为十秒,我们减少了看到requests.exceptions.ReadTimeout错误的风险,这种错误偶尔会发生,如果 Google Trends 需要更长时间来响应的话。如果仍然看到此错误,只需再次运行函数或增加超时时间。

我们为请求对象提供kw_listtimeframe参数。

通过调用interest_over_time来发出实际请求,它将返回一个 pandas DataFrame。

我们使用其可读的人类等价物重新命名mids

最后一列称为isPartialTrue表示当前间隔(例如一周)仍在进行中,因此尚未完全具备所有数据。为了保持简单并与在线版本保持一致,我们在返回 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,对应于调用函数的单元格(以 xlwings range对象的形式)。通过使用pictures.addtopleft参数,可以轻松地放置图形。name参数将定义 Excel 中图片的名称。

我们设置了seaborn风格,使绘图在视觉上更具吸引力。

只有在 DataFrame 不为空时才调用plot方法。在空 DataFrame 上调用plot方法会引发错误。

get_figure()从 DataFrame 绘图返回 Matplotlib 图形对象,这是pictures.add所期望的。

参数topleft仅在首次插入图时使用。提供的参数将在方便的位置放置图——即从调用此函数的单元格下一行。

参数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 的调试器),您需要完成两件事:

  1. 在 Excel 插件中,激活复选框 Debug UDFs。这样可以防止 Excel 自动启动 Python,这意味着您需要按照下一个步骤手动进行操作。

  2. 手动运行 Python UDF 服务器的最简单方法是在您要调试的文件的最底部添加以下几行。我已经在配套仓库的 google_trends.py 文件的底部添加了这些行:

  3. 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 函数中的差异很小,只在参数装饰器中有所不同:基于数组的版本将usersprices作为 NumPy 数组读取进来——这里唯一的注意事项是通过在参数装饰器中设置ndim=2users读入为二维列向量。您可能还记得,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_cacheimport``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 查看可用的宏:除了使用 RunPythonSampleCall 外,现在还会看到一个名为 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 案例研究来影响函数参数和返回值的行为,您学会了如何使用argret装饰器分别来影响函数参数和返回值的行为。最后一部分向您展示了一些性能技巧,并介绍了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 将分别是 car1car2

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.attributemyobject.method()

使用时区感知的 datetime 对象

在第三章中,我们简要介绍了无时区感知的 datetime 对象。如果时区很重要,通常在 UTC 时区中工作,仅在显示目的时转换为本地时区。UTC 代表协调世界时,是格林威治平均时间(GMT)的后继者。在使用 Excel 和 Python 时,您可能希望将 Excel 提供的无时区时间戳转换为时区感知的 datetime 对象。在 Python 中支持时区时,可以使用 dateutil 包,它不是标准库的一部分,但已预装在 Anaconda 中。以下示例展示了在处理 datetime 对象和时区时的几种常见操作:

`In[12]:importdatetimeasdtfromdateutilimporttz

`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模块的形式添加了适当的时区支持。使用它替换来自dateutiltz.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=aa[1]=22print(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]:importcopyb=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。

  1. 前言

    1. 为什么写这本书

    2. 这本书适合谁

    3. 本书组织结构

    4. Python 和 Excel 版本

    5. 本书使用的约定

    6. 使用代码示例

    7. O’Reilly 在线学习

    8. 如何联系我们

    9. 致谢

  2. I. Python 简介

  3. 1. 为什么选择 Python 处理 Excel?

    1. Excel 是一种编程语言

      1. Excel 在新闻中

      2. 编程最佳实践

      3. 现代 Excel

    2. Python 处理 Excel

      1. 可读性和可维护性

      2. 标准库和包管理器

      3. 科学计算

      4. 现代语言特性

      5. 跨平台兼容性

    3. 结论

  4. 2. 开发环境

    1. Anaconda Python 发行版

      1. 安装

      2. Anaconda Prompt

      3. Python REPL:交互式 Python 会话

      4. 包管理器:Conda 和 pip

      5. Conda 环境

    2. Jupyter 笔记本

      1. 运行 Jupyter 笔记本

      2. 笔记本单元格

      3. 编辑模式与命令模式

      4. 运行顺序很重要

      5. 关闭 Jupyter 笔记本

    3. Visual Studio Code

      1. 安装和配置

      2. 运行 Python 脚本

    4. 结束语

  5. 3. Python 入门

    1. 数据类型

      1. 对象

      2. 数值类型

      3. 布尔值

      4. 字符串

    2. 索引和切片

      1. 索引

      2. 切片

    3. 数据结构

      1. 列表

      2. 字典

      3. 元组

      4. 集合

    4. 控制流

      1. 代码块和pass语句

      2. if语句和条件表达式

      3. forwhile循环

      4. 列表、字典和集合推导

    5. 代码组织

      1. 函数

      2. 模块和import语句

      3. datetime

    6. PEP 8:Python 代码风格指南

      1. PEP 8 和 VS Code

      2. 类型提示

    7. 结束语

  6. II. pandas 简介

  7. 4. NumPy 基础

    1. NumPy 入门

      1. NumPy 数组

      2. 向量化和广播

      3. 通用函数(ufunc

    2. 创建和操作数组

      1. 获取和设置数组元素

      2. 有用的数组构造器

      3. 视图 vs. 复制

    3. 结论

  8. 5. 使用 pandas 进行数据分析

    1. DataFrame 和 Series

      1. 索引

      2. 列操作

    2. 数据操作

      1. 选择数据

      2. 设置数据

      3. 缺失数据

      4. 重复数据

      5. 算术操作

      6. 处理文本列

      7. 应用函数

      8. 视图 vs. 复制

    3. 合并数据框

      1. 连接操作

      2. 连接和合并

    4. 描述性统计和数据聚合

      1. 描述性统计

      2. 分组

      3. 透视表和融合

    5. 绘图

      1. Matplotlib

      2. Plotly

    6. 导入和导出数据框

      1. 导出 CSV 文件

      2. 导入 CSV 文件

    7. 结论

  9. 6. 使用 pandas 进行时间序列分析

    1. 日期时间索引

      1. 创建日期时间索引

      2. 过滤日期时间索引

      3. 处理时区

    2. 常见时间序列操作

      1. 移动和百分比变化

      2. 重新基准化和相关性

      3. 重采样

      4. 滚动窗口

    3. pandas 的限制

    4. 结论

  10. III. 无 Excel 读写 Excel 文件

  11. 7. 使用 pandas 进行 Excel 文件操作

    1. 案例研究:Excel 报告

    2. 使用 pandas 读写 Excel 文件

      1. read_excel 函数和 ExcelFile 类

      2. to_excel 方法和 ExcelWriter 类

    3. 使用 pandas 与 Excel 文件时的限制

    4. 结论

  12. 8. 使用读写包操作 Excel 文件

    1. 读写包

      1. 何时使用哪个包

      2. excel.py 模块

      3. OpenPyXL

      4. XlsxWriter

      5. pyxlsb

      6. xlrd, xlwt 和 xlutils

    2. 高级读写主题

      1. 处理大型 Excel 文件

      2. 在 Excel 中格式化数据框

      3. 案例研究(再次审视):Excel 报告

    3. 结论

  13. IV. 使用 xlwings 编程 Excel 应用

  14. 9. Excel 自动化

    1. 开始使用 xlwings

      1. 将 Excel 用作数据查看器

      2. Excel 对象模型

      3. 运行 VBA 代码

    2. 转换器、选项和集合

      1. 处理数据框

      2. 转换器和选项

      3. 图表、图片和定义名称

      4. 案例研究(再次审视):Excel 报告

    3. 高级 xlwings 主题

      1. xlwings 基础

      2. 提高性能

      3. 如何处理缺失功能

    4. 结论

  15. 10. 使用 Python 扩展 Excel 工具

    1. 使用 xlwings 将 Excel 用作前端

      1. Excel 加载项

      2. 快速启动命令

      3. 运行主程序

      4. RunPython 函数

    2. 部署

      1. Python 依赖

      2. 独立工作簿:摆脱 xlwings 加载项

      3. 配置层次结构

      4. 设置

    3. 结论

  16. 11. Python 包跟踪器

    1. 我们将构建什么

    2. 核心功能

      1. Web API

      2. 数据库

      3. 异常

    3. 应用程序结构

      1. 前端

      2. 后端

      3. 调试

    4. 结论

  17. 12. 用户定义函数 (UDFs)

    1. UDF 入门

      1. UDF 快速入门
    2. 案例研究:Google 趋势

      1. 介绍 Google 趋势

      2. 处理数据框和动态数组

      3. 从 Google 趋势获取数据

      4. 使用 UDF 绘图

      5. 调试 UDF

    3. 高级 UDF 主题

      1. 基本性能优化

      2. 缓存

      3. 子装饰器

    4. 结论

  18. A. Conda 环境

    1. 创建新的 Conda 环境

    2. 禁用自动激活

  19. B. 高级 VS Code 功能

    1. 调试器

    2. VS Code 中的 Jupyter 笔记本

      1. 运行 Jupyter 笔记本

      2. 带有代码单元的 Python 脚本

  20. C. 高级 Python 概念

    1. 类和对象

    2. 使用时区感知的 datetime 对象

    3. 可变 vs. 不可变 Python 对象

      1. 调用具有可变对象作为参数的函数

      2. 具有可变对象作为默认参数的函数

  21. 索引

posted @ 2024-06-17 17:11  绝不原创的飞龙  阅读(34)  评论(0编辑  收藏  举报