Python 时间序列分析秘籍第二版(一)

原文:annas-archive.org/md5/7277c6f80442eb633bdbaf16dcd96fad

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章:1 开始时间序列分析

加入我们在 Discord 上的书籍社区

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file0.png

packt.link/zmkOY

当你开始学习Python编程时,你经常会按照指示安装包并导入库,然后进入一段跟着代码走的学习流程。然而,在任何数据分析或数据科学过程中,一个常常被忽视的部分就是确保正确的开发环境已经搭建好。因此,从一开始就打好基础是至关重要的,这样可以避免未来可能出现的麻烦,比如实现过于复杂、包冲突或依赖危机。搭建好合适的开发环境会在你完成项目时帮助你,确保你能够以可重现和生产就绪的方式交付成果。

这样的主题可能不会那么有趣,甚至可能会感觉有些行政负担,而不是直接进入核心主题或当前的项目。但正是这个基础,才将一个经验丰富的开发者与其他人区分开来。就像任何项目一样,无论是机器学习项目、数据可视化项目,还是数据集成项目,一切都始于规划,并确保在开始核心开发之前,所有需要的部分都已经到位。

本章中,你将学习如何设置Python 虚拟环境,我们将介绍两种常见的方法来实现这一点。步骤将涵盖常用的环境和包管理工具。本章旨在实践操作,避免过多的行话,并让你以迭代和有趣的方式开始创建虚拟环境。

随着本书的进展,你将需要安装多个特定于时间序列分析时间序列可视化机器学习深度学习(针对时间序列数据)的 Python 库。不管你有多大的诱惑跳过这一章,都不建议这么做,因为它将帮助你为随后的任何代码开发打下坚实的基础。在这一章结束时,你将掌握使用condavenv创建和管理 Python 虚拟环境所需的技能。

本章将涵盖以下内容:

  • 开发环境设置

  • 安装 Python 库

  • 安装 JupyterLab 和 JupyterLab 扩展

技术要求

本章中,你将主要使用命令行。对于 macOS 和 Linux,默认的终端将是 (bashzsh),而在 Windows 操作系统中,你将使用Anaconda 提示符,这是 Anaconda 或 Miniconda 安装包的一部分。关于如何安装 Anaconda 或 Miniconda,将在随后的准备工作部分讨论。

我们将使用 Visual Studio Code 作为 IDE,它可以免费获取,网址为 code.visualstudio.com。它支持 Linux、Windows 和 macOS。

其他有效的替代选项也可以让你跟随学习,具体包括:

本章的源代码可以在 github.com/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook 获取

开发环境设置

当我们深入本书提供的各种配方时,你将会创建不同的 Python 虚拟环境,以便安装所有依赖项而不影响其他 Python 项目。

你可以将虚拟环境看作是独立的桶或文件夹,每个文件夹都包含一个 Python 解释器和相关的库。下面的图示说明了独立的自包含虚拟环境的概念,每个虚拟环境都有不同的 Python 解释器,并且安装了不同版本的包和库:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file1.jpg

图 1.1:三个不同 Python 虚拟环境的示例,每个环境对应一个 Python 项目

如果你安装了 Anaconda,那么这些环境通常会存储在 envs 子文件夹中的单独文件夹内,该子文件夹位于主 Anaconda(或 Miniconda)文件夹的安装目录下。举例来说,在 macOS 上,你可以在 Users/<yourusername>/opt/anaconda3/envs/ 路径下找到 envs 文件夹。在 Windows 操作系统中,路径可能看起来像 C:\Users\<yourusername>\anaconda3\envs。如果你安装的是 Miniconda,那么 main 文件夹将是 miniconda3 而不是 anaconda3

每个环境(文件夹)都包含一个 Python 解释器,该解释器在创建环境时指定,例如 Python 2.7.18 或 Python 3.9 解释器。

一般来说,如果没有测试作为策略的一部分,升级 Python 版本或包可能会导致许多不希望出现的副作用。常见的做法是复制当前的 Python 环境,在进行所需的升级并进行测试之后,再决定是否继续进行升级。这正是环境管理器(condavenv)和包管理器(condapip)在你的开发和生产部署过程中所带来的价值。

准备工作

在本节中,假设你已经通过以下任一方式安装了最新版本的 Python:

截至撰写时,最新的 Python 版本是 Python 3.11.3。

Anaconda 支持的最新 Python 版本

Anaconda 的最新版本是 2023.03,于 2023 年 4 月发布。默认情况下,Anaconda 会将 Python 3.10.9 作为基础解释器。此外,你可以使用 conda create 创建一个 Python 版本为 3.11.3 的虚拟环境,稍后你将在本食谱中看到如何操作。

获取快速顺利启动的最简单有效方法是使用像AnacondaMiniconda这样的 Python 发行版。如果你是初学者,我甚至更推荐使用 Anaconda。

如果你是 macOS 或 Linux 用户,一旦安装了 Anaconda,你几乎可以直接使用默认的终端。要验证安装情况,打开终端并输入以下命令:

$ conda info

以下截图展示了运行 conda info 时的标准输出,列出了有关已安装 conda 环境的信息。你应该关注列出的 conda 和 Python 的版本:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file2.png

图 1.2 – 在 Linux(Ubuntu)上使用终端验证 Conda 的安装

如果你在 Windows 操作系统上安装了 Anaconda,你需要使用 Anaconda Prompt。要启动它,你可以在 Windows 搜索栏中输入 Anaconda,并选择列出的其中一个 Anaconda Prompt(Anaconda PromptAnaconda PowerShell Prompt)。一旦启动了 Anaconda Prompt,你可以运行 conda info 命令。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file3.png

图 1.3:使用 Anaconda Prompt 在 Windows 上验证 Conda 的安装

如何操作…

在本指南中,我将介绍两个流行的环境管理工具。如果你已经安装了 Anaconda、Miniconda 或 Miniforge,那么conda应该是你的首选,因为它为 Python(并且支持许多其他语言)提供了包依赖管理环境管理。另一方面,另一个选项是使用venv,这是一个内置的 Python 模块,提供环境管理,无需额外安装。

condavenv都允许你为你的 Python 项目创建多个虚拟环境,这些项目可能需要不同版本的 Python 解释器(例如,3.4、3.8 或 3.9)或不同的 Python 包。此外,你可以创建一个沙箱虚拟环境来尝试新的包,以了解它们如何工作,而不会影响你基础的 Python 安装。

为每个项目创建一个单独的虚拟环境是许多开发者和数据科学实践者采纳的最佳实践。遵循这一建议从长远来看会对你有所帮助,避免在安装包时遇到常见问题,如包依赖冲突。

使用 Conda

首先打开你的终端(Windows 的 Anaconda 提示符):

  1. 首先,让我们确认你拥有最新版本的conda。你可以通过以下命令来做到这一点:
$ conda update conda

上面的代码将更新 conda 包管理器。如果你使用的是现有的安装,这将非常有用。通过这种方式,你可以确保拥有最新版本。

  1. 如果你已经安装了 Anaconda,可以使用以下命令更新到最新版本:
$ conda update anaconda
  1. 现在,你将创建一个名为py310的新虚拟环境,指定的 Python 版本为 Python 3.10:
$ conda create -n py310 python=3.10

在这里,-n--name的快捷方式。

  1. conda可能会识别出需要下载和安装的其他包。系统可能会提示你是否继续。输入y然后按Enter键继续。

  2. 你可以通过添加-y选项跳过前面步骤中的确认消息。如果你对自己的操作有信心,并且不需要确认消息,可以使用此选项,让conda立即继续而不提示你进行响应。你可以通过添加-y--yes选项来更新你的命令,如以下代码所示:

$ conda create -n py10 python=3.10 -y
  1. 一旦设置完成,你就可以激活新的环境。激活一个 Python 环境意味着我们的**$PATH环境变量会被更新,指向虚拟环境(文件夹)中的指定 Python 解释器。你可以使用echo**命令来确认这一点:
$ echo $PATH
> /Users/tarekatwan/opt/anaconda3/bin:/Users/tarekatwan/opt/anaconda3/condabin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

上面的代码适用于 Linux 和 macOS。如果你使用的是 Windows Anaconda 提示符,可以使用echo %path%。在 Anaconda PowerShell 提示符中,你可以使用echo $env:path

在这里,我们可以看到我们的$PATH变量指向的是基础的conda环境,而不是我们新创建的虚拟环境。

  1. 现在,激活你新的 py310 环境,并再次测试 $PATH 环境变量。你会注意到它现在指向 envs 文件夹——更具体地说,是 py310/bin 子文件夹:
$ conda activate py39
$ echo $PATH
> /Users/tarekatwan/opt/anaconda3/envs/py310/bin:/Users/tarekatwan/opt/anaconda3/condabin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
  1. 另一种确认我们新虚拟环境是活动环境的方法是运行以下命令:
$ conda info –envs

上面的命令将列出所有已创建的 conda 环境。请注意,py310 前面有一个 *,表示它是当前活动环境。以下截图显示我们有四个虚拟环境,且 py310 是当前活动的环境:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file4.png

图 1.4:在 MacOS 上通过 conda 创建的所有 Python 虚拟环境列表

  1. 一旦激活了特定环境,你安装的任何软件包将只会在该隔离环境中可用。例如,假设我们安装 pandas 库并指定要在 py310 环境中安装哪个版本。写作时,pandas 2.0.1 是最新版本:
$ conda install pandas=2.0.1

请注意,conda 会再次提示你确认,并告知将下载并安装哪些额外的软件包。在这里,conda 正在检查 pandas 2.0.1 所需的所有依赖项,并为你安装它们。你还可以通过在命令末尾添加 -y--yes 选项来跳过此确认步骤。

消息还会指出安装将发生的环境位置。以下是安装 pandas 2.0.1 时的一个提示消息示例:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file5.png

图 1.5:Conda 确认提示,列出所有软件包

如果你遇到 PackagesNotFoundError 错误,你可能需要添加 conda-forge 通道来安装最新版本的 pandas(例如 2.0.1)。你可以使用以下命令来完成此操作:

$ conda config --add channels conda-forge

Conda-Forge 提供适用于不同平台和架构的构建,并会自动选择适合你平台和架构的构建版本。

作为示例,如果你想为 MacOS ARM 指定一个 conda-forge 构建版本,你可以按照以下方式指定该构建:

$ conda config --add channels conda-forge/osx-arm64

  1. 一旦你按下 y 并按 Enter 键,conda 将开始下载并安装这些软件包。

  2. 一旦你完成在当前 py310 环境中的工作,你可以使用以下命令 deactivate 来退出并返回到基础 Python 环境:

$ conda deactivate
  1. 如果你不再需要 py310 环境并希望删除它,你可以使用 env remove 命令来删除它。该命令将完全删除该环境及所有已安装的库。换句话说,它将删除(移除)该环境的整个文件夹:
$ conda env remove -n py310
使用 venv

一旦安装了 Python 3x,你可以使用内置的venv模块,该模块允许你创建虚拟环境(类似于conda)。注意,当使用venv时,你需要提供一个路径,以指定虚拟环境(文件夹)创建的位置。如果没有提供路径,虚拟环境将会在你运行命令的当前目录下创建。在下面的代码中,我们将在Desktop目录中创建虚拟环境。

按照以下步骤创建新环境、安装包并使用venv删除该环境:

  1. 首先,决定你希望将新的虚拟环境放在哪里,并指定路径。在这个示例中,我已经导航到Desktop并运行了以下命令:
$ cd Desktop
$ python -m venv py310

上面的代码将在Desktop目录中创建一个新的 py310 文件夹。py310文件夹包含若干子目录、Python 解释器、标准库以及其他支持文件。该文件夹结构类似于condaenvs目录中创建的环境文件夹。

  1. 让我们激活 py310 环境,并检查$PATH 环境变量以确认它已经被激活。以下脚本适用于 Linux 和 macOS(bash 或 zsh),假设你是在 Desktop 目录下运行命令:
$ source py310/bin/activate
$ echo $ PATH
> /Users/tarekatwan/Desktop/py310/bin:/Users/tarekatwan/opt/anaconda3/bin:/Users/tarekatwan/opt/anaconda3/condabin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

在这里,我们可以看到py310环境已经被激活。

在 Windows 上使用 Anaconda PowerShell Prompt 时,没有bin子文件夹,因此你需要使用以下语法运行命令,假设你是在Desktop目录下运行命令:

$ .py310\Scripts\activate

在 Scripts 文件夹下有两个激活文件:activate.batActivate.ps1,后者应在 Anaconda PowerShell Prompt 中使用,而不是 Anaconda Windows 命令提示符。通常在 PowerShell 中,如果你省略文件扩展名,正确的脚本会被执行。但最好是指定正确的文件扩展名,例如指定Activate.ps1,如下所示:

.\py310\Scripts\Activate.ps1
  1. 现在,让我们使用以下命令检查已安装的版本:
$ python --version
> Python 3.10.10
  1. 一旦完成使用py310环境进行开发,你可以使用deactivate命令将其停用,返回到基础 Python 环境:
$ deactivate
  1. 如果你不再需要py310环境并希望删除它,只需删除整个py310文件夹即可。

它是如何工作的…

一旦虚拟环境被激活,你可以验证当前活跃的 Python 解释器位置,以确认你正在使用正确的解释器。之前,你看到激活虚拟环境后,$PATH环境变量是如何变化的。在 Linux 和 macOS 中,你可以使用which命令实现类似的效果,在 Windows PowerShell 中使用Get-Command,或者在 Windows 命令提示符中使用where命令。

以下是 macOS 或 Linux 的示例:

$ which python
> /Users/tarekatwan/opt/anaconda3/envs/py310/bin/python

以下是 Windows 操作系统(PowerShell)的示例:

$ where.exe python

where命令的替代方法是Get-Command,如下所示:

$ Get-Command python

这些命令将输出活动 Python 解释器的路径。前述命令的输出将显示不同的路径,具体取决于活动环境是使用conda还是venv创建的。激活conda虚拟环境时,它将位于envs文件夹中,如 MacOS 上所示:

/Users/tarekatwan/opt/anaconda3/envs/py310/bin/python

当激活venv虚拟环境时,路径将与创建时提供的路径相同,如 MacOS 上的示例所示:

/Users/tarekatwan/Desktop/py310/bin/python

您在激活虚拟环境后安装的任何额外包或库都将与其他环境隔离,并保存在该环境的文件夹结构中。

如果我们比较venvconda的文件夹结构,您会看到相似之处,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file6.png

图 1.6:使用 conda 和 venv 比较文件夹结构

请回忆,当使用conda时,所有环境默认位于anaconda3/minconda3/目录下的/envs/位置。而使用venv时,您需要提供一个路径来指定创建目录或项目的位置;否则,它将默认为您运行命令时所用的当前目录。类似地,您可以使用conda通过选项-p--prefix指定不同的路径。请注意,使用venv时,您无法指定 Python 版本,因为它依赖于当前活动或基础 Python 版本来运行命令。这与conda不同,conda允许您指定不同的 Python 版本,而不管基础Python 版本是什么。例如,当前基础环境的 Python 版本是 3.10,您仍然可以使用以下命令创建一个 3.11.x 环境:

conda create -n py311 python=3.11 -y

上述代码将创建一个新的py311环境,并安装 Python 3.11.3 版本。

conda的另一个优点是,它提供了两个功能:包和依赖管理器以及虚拟环境管理器。这意味着我们可以使用相同的conda环境,通过conda create创建额外的环境,并使用conda install <package name>安装包,这将在下一个章节“安装 Python 库”中使用。

请记住,使用venv时,它是一个虚拟环境管理器,您仍然需要依赖pip作为包管理器来安装包;例如,使用pip install <package name>

此外,当使用conda安装包时,它会检查是否存在冲突,并会提示您进行任何推荐操作,包括是否需要升级、降级或安装额外的包依赖。

最后,使用conda的一个额外好处是,您不仅可以为 Python 创建环境,还可以为其他语言创建环境。包括 Julia、R、Lua、Scala、Java 等。

还有更多…

在前面的示例中,你可以使用condavenv从零开始创建 Python 虚拟环境。你创建的虚拟环境可能还不包含所需的包,因此你需要特别为项目安装这些包。你将会在接下来的食谱“安装 Python 库”中了解如何安装包。

还有其他方式可以在conda中创建虚拟环境,我们将在这里讨论这些方式。

使用 YAML 文件创建虚拟环境

你可以从YAML文件创建虚拟环境。这个选项可以让你在一步中定义环境的多个方面,包括应该安装的所有包。

你可以在 VSCode 中创建一个 YAML 文件。以下是一个env.yml文件的示例,使用 Python 3.10 创建一个名为tscookbookconda环境:

# A YAML for creating a conda environment
# file: env.yml
# example creating an environment named tscookbook
name: tscookbook
channels:
  - conda-forge
  - defaults
dependencies:
  - python=3.10
  - pip
  # Data Analysis
  - statsmodels
  - scipy
  - pandas
  - numpy
  - tqdm
  # Plotting
  - matplotlib
  - seaborn
  # Machine learning
  - scikit-learn
  - jupyterlab

要使用env.yml文件创建虚拟环境,可以使用conda env create命令,并添加-f--file选项,如下所示:

$ conda env create -f env.yml

一旦这个过程完成,你可以激活该环境:

$ conda activate tscookbook

你还可以从现有环境引导生成 YAML 文件。如果你想与他人分享环境配置或为将来使用创建备份,这个方法非常有用。以下三个命令将使用稍微不同的语法选项,达到将py310conda环境导出为env.yml的 YAML 文件的相同效果:

$ conda env export -n py310 > env.yml
$ conda env export -n py310 -f env.yml
$ conda env export –name py310 –file env.yml

这将在当前目录下为你生成env.yml文件。

从另一个环境克隆虚拟环境

这是一个很棒的功能,适用于你想要尝试新包或升级现有包,但又不希望破坏当前项目中的现有代码。使用–clone选项,你可以一步创建环境的副本或克隆。这样可以实现与前面使用conda env export命令生成现有环境的 YAML 文件,再基于该 YAML 文件创建新环境相同的效果。以下示例将把py310conda环境克隆为一个名为py310_clone的新环境:

$ conda create --name py310_clone --clone py310

另见

值得一提的是,Anaconda 附带了一个工具,叫做 anaconda-project,它可以将你的conda项目工件打包并创建一个 YAML 文件以确保可重复性。这个工具非常适合创建、共享和确保数据科学项目的可重复性。可以把它看作是手动编写 YAML 的替代方案。更多信息,请参考官方 GitHub 仓库:github.com/Anaconda-Platform/anaconda-project

要查看参数列表,你可以在终端中输入以下命令:

$ anaconda-project --help

如果你正在使用一台不允许安装软件的机器,或者使用一台性能有限的旧机器,不用担心。还有其他选项可以让你在本书中进行动手实践。

你可以探索的一些替代选项如下:

  • Google Colab 是一个基于云的平台,允许你在笔记本中编写和运行 Python 代码,这些笔记本已经预装了一些最受欢迎的数据科学包,包括pandasstatsmodelsscikit-learnTensorFlow。Colab 允许你通过 pip install 在笔记本中安装额外的包。Colab 的一个好特点是你可以选择配置你的笔记本,以便免费使用 CPU、GPU 或 TPU。你可以通过访问 colab.research.google.com/ 来探索 Colab。

  • Kaggle Notebooks 类似于 Colab,包含托管的 Jupyter 笔记本,并且已经预装了许多最受欢迎的数据科学包。它也允许你通过 pip install 安装任何需要的额外包。有关更多信息,请参考 www.kaggle.com/docs/notebooks

  • Replit 提供一个免费的在线 IDE,支持超过 50 种语言,包括 Python。你只需创建一个帐户,并通过访问 replit.com/ 来创建你的新 replit 空间。

  • Binder 是一个在线开源平台,允许你将 Git 仓库转化为一组交互式笔记本。你可以通过访问 mybinder.org 来探索 Binder。

  • Deepnote 类似于 Colab,是一个在线平台,允许你编写、运行和协作 Python 笔记本,并提供了一个免费的计划,你可以在这里查看 deepnote.com

安装 Python 库

在之前的配方中,你已经了解了 YAML 环境配置文件,它允许你通过一行代码一步创建一个 Python 虚拟环境和所有必要的包。

$ conda env create -f env.yml

在本书的整个过程中,你需要安装若干个 Python 库来跟随本书的配方。你将探索几种安装 Python 库的方法。

准备工作

在本书中,你将创建并使用不同的文件,包括 requirements.txtenvironment_history.yml 和其他文件。这些文件可以从本书的 GitHub 仓库中下载:github.com/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook/tree/main/code/Ch1

在本章中,你将学习如何生成你的 requirements.txt 文件,并了解如何安装库的一般过程。

如何做…

一次性安装一组库的最简单方法是使用 requirements.txt 文件。

简而言之,requirements.txt文件列出了你想要安装的 Python 库及其相应的版本。你可以手动创建requirements.txt文件,也可以从现有的 Python 环境中导出它。

该文件不一定需要命名为requirements.txt,它更多的是一种命名约定,并且是 Python 社区非常常见的一种命名方式。一些工具,如 PyCharm,如果requirements.txt文件位于项目的根目录中,会自动检测到该文件。

使用 conda

使用conda时,你有不同的选择来批量安装包。你可以创建一个新的环境,并一次性安装requirements.txt文件中列出的所有包(使用conda create命令),或者你可以使用requirements.txt文件将 Python 包安装到现有的环境中(使用conda install命令):

  • 选项 1:创建一个新的conda环境,并一步到位安装库。例如,你可以为每一章创建一个新的环境,并使用关联的requirements.txt文件。以下示例将创建一个名为ch1的新环境,并安装requirements.txt文件中列出的所有包:
$ conda create --name ch1 -f requirements.txt
  • 选项 2:将所需的库安装到现有的conda环境中。在此示例中,你有一个现有的timeseries环境,首先需要激活该环境,然后从requirements.txt文件中安装库:
$ conda activate timeseries
$ conda install -f requirements.txt
使用 venv 和 pip

由于venv只是一个环境管理器,你将需要使用pip作为包管理工具。你将首先使用venv创建一个新环境,然后使用pip安装包:

  • MacOS/Linux上:创建并激活venv环境,然后再安装包:
$ python -m venv Desktopn/timeseries
$ source Desktop/timeseries/bin/activate
$ pip install -r requirements.txt
  • Windows上:创建并激活venv环境,然后安装包:
$ python -m venv .\Desktop\timeseries
$ .\Desktop\timeseries\Scripts\activate
$ pip install -r requirements.txt

注意,在前面的 Windows 代码中,没有指定activate文件的扩展名(.bat.ps1)。这是有效的,能够在 Windows 命令提示符或 PowerShell 中工作。

它是如何工作的…

在前面的代码中,提供了requirements.txt文件,以便你可以安装所需的库。

那么,如何生成你的requirements.txt文件呢?

创建requirements.txt文件有两种方法。让我们来看看这两种方法。

手动创建文件

由于它是一个简单的文件格式,你可以使用任何文本编辑器来创建该文件,例如 VSCode,并列出你想要安装的包。如果你没有指定包的版本,那么安装时将考虑最新版本。请参见以下simple.txt文件的示例:

pandas==1.4.2
matplotlib

首先,让我们测试一下venvpip。运行以下脚本(我在 Mac 上运行此操作):

$ python -m venv ch1
$ source ch1/bin/activate
$ pip install -r simple.txt
$ pip list
Package         Version
--------------- -------
cycler          0.11.0
fonttools       4.33.3
kiwisolver      1.4.2
matplotlib      3.5.2
numpy           1.22.4
packaging       21.3
pandas          1.4.2
Pillow          9.1.1
pip             22.0.4
pyparsing       3.0.9
python-dateutil 2.8.2
pytz            2022.1
setuptools      58.1.0
six             1.16.0
$ deactivate

那些额外的包是什么?这些是基于pip为我们识别和安装的pandasmatplotlib的依赖关系。

现在,让我们这次使用conda,并使用相同的simple.txt文件:

$ conda create -n ch1  --file simple.txt python=3.10

安装完成后,您可以激活环境并列出已安装的包:

$ conda activate ch1
$ conda list

您可能会注意到列表相当庞大。与 pip 方法相比,安装了更多的包。您可以使用以下命令来计算已安装的库数:

$ conda list | wc -l
> 54

这里有几个需要注意的事项:

  • conda 从 Anaconda 仓库以及 Anaconda 云安装包。

  • pipPython 包索引(PyPI) 仓库安装包。

  • conda 会对它计划下载的所有包进行非常彻底的分析,并且在处理版本冲突时比 pip 做得更好。

引导文件

第二个选项是从现有环境中生成 requirements.txt 文件。当您为将来的使用重建环境,或与他人共享包和依赖关系列表时,这非常有用,可以确保可重现性和一致性。假设您在一个项目中工作并安装了特定的库,您希望确保在共享代码时,其他用户能够安装相同的库。这时,生成 requirements.txt 文件就显得非常方便。同样,导出 YAML 环境配置文件的选项之前已经展示过。

让我们看看如何在pipconda中执行此操作。请记住,两种方法都会导出已经安装的包及其当前版本的列表。

venv 和 pip freeze

pip freeze 允许您导出环境中所有通过 pip 安装的库。首先,激活之前用 venv 创建的 ch1 环境,然后将包列表导出到 requirements.txt 文件中。以下示例是在 macOS 上使用终端:

$ source ch1/bin/activate
$ pip freeze > requirements.txt
$ cat requirements.txt
>>>
cycler==0.11.0
fonttools==4.33.3
kiwisolver==1.4.2
matplotlib==3.5.2
numpy==1.22.4
...

完成后,您可以运行 deactivate 命令。

Conda

让我们激活之前使用 conda 创建的环境(ch1 环境),并导出已安装的包列表:

$ conda activate ch1
$ conda list -e > conda_requirements.txt
$ cat conda_requirements.txt
>>>
# This file may be used to create an environment using:
# $ conda create --name <env> --file <this file>
# platform: osx-64
blas=1.0=mkl
bottleneck=1.3.4=py39h67323c0_0
brotli=1.0.9=hb1e8313_2
ca-certificates=2022.4.26=hecd8cb5_0
certifi=2022.5.18.1=py39hecd8cb5_0
cycler=0.11.0=pyhd3eb1b0_0
...

还有更多……

当您使用 conda 导出已安装包的列表时,conda_requirements.txt 文件包含了大量的包。如果您只想导出您显式安装的包(而不是 conda 添加的附加包),则可以使用带有 --from-history 标志的 conda env export 命令:

$ conda activate ch1
$ conda env export --from-history > env.yml
$ cat env.yml
>>>
name: ch1
channels:
  - defaults
dependencies:
  - matplotlib
  - pandas==1.2.0
prefix: /Users/tarek.atwan/opt/anaconda3/envs/ch1

请注意,您不必像之前那样先激活环境。相反,您可以添加 -n--name 选项来指定环境的名称。否则,它将默认为当前激活的环境。这是修改后的脚本示例:

conda env export -n ch1 --from-history > env.yml

另请参见

安装 JupyterLab 和 JupyterLab 扩展

在本书中,你可以使用自己喜欢的 Python IDE(例如 PyCharm 或 Spyder)或文本编辑器(例如 Visual Studio Code、Atom 或 Sublime)进行操作。另一种选择是基于笔记本概念的交互式学习,它通过 Web 界面进行。更具体地说,Jupyter NotebookJupyter Lab 是学习、实验和跟随本书教程的首选方法。有趣的是,Jupyter 这个名字来源于三种编程语言:Julia、Python 和 R。或者,你也可以使用 Google 的 Colab 或 Kaggle Notebooks。欲了解更多信息,请参考本章 开发环境设置 教程中的 另见 部分。如果你不熟悉 Jupyter Notebooks,可以在这里了解更多信息:jupyter.org/

在本教程中,你将安装 Jupyter Notebook、JupyterLab 以及额外的 JupyterLab 扩展。

此外,你还将学习如何安装单个软件包,而不是像前面教程中那样采用批量安装方法。

在后续示例中使用 CONDA

在后续的操作中,当新环境被创建时,代码将使用 conda 进行编写。前面的教程已经涵盖了创建虚拟环境的两种不同方法(venvconda)以及安装软件包的两种方法(pipconda),这将允许你根据自己的选择继续操作。

准备就绪

我们将创建一个新环境并安装本章所需的主要软件包,主要是 pandas:

$ conda create -n timeseries python=3.9 pandas -y

这段代码创建了一个名为 timeseries 的新的 Python 3.9 环境。语句的最后部分列出了你将要安装的各个软件包。如果软件包列表较大,建议使用 requirements.txt 文件。如果软件包不多,可以直接用空格分隔列出,如下所示:

$ conda create -n timeseries python=3.9 pandas matplotlib statsmodels -y

一旦环境创建完毕并且软件包安装完成,可以继续激活该环境:

$ conda activate timeseries

操作方法……

现在我们已经创建并激活了环境,接下来安装 Jupyter:

  1. 现在我们已经激活了环境,可以使用 conda install 安装 conda create 时未包含的其他软件包:
$ conda install jupyter -y
  1. 你可以通过输入以下命令启动 JupyterLab 实例:
$ jupyter lab

请注意,这会运行一个本地 Web 服务器,并在你的默认浏览器中启动 JupyterLab 界面,指向 localhost:8888/lab。以下截图展示了你在终端中输入上述代码后看到的类似界面:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file7.jpg

图 1.6:启动 JupyterLab 将运行本地 Web 服务器

  1. 要终止 Web 服务器,请在终端中按 Ctrl + C 两次,或者在 Jupyter GUI 中点击 Shut Down,如以下截图所示:

    https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file8.jpg

    图 1.7:关闭 JupyterLab 网络服务器

  2. 现在,你可以安全地关闭浏览器。

  3. 请注意,在前面的例子中,当 JupyterLab 启动时,它是在默认浏览器中启动的。如果你希望使用不同的浏览器,可以像这样更新代码:

$ jupyter lab --browser=chrome

在这个例子中,我指定了我要在 Chrome 上启动,而不是 Safari,这是我机器上的默认浏览器。你可以将值更改为你偏好的浏览器,比如 Firefox、Opera、Chrome 等。

在 Windows 操作系统中,如果前面的代码没有自动启动 Chrome,你需要使用webbrowser.register()注册浏览器类型。为此,首先使用以下命令生成 Jupyter Lab 配置文件:

jupyter-lab --generate-config

打开jupyter_lab_config.py文件,并在顶部添加以下内容:

import webbrowser
webbrowser.register('chrome', None, webbrowser.GenericBrowser('C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'))

保存并关闭文件。你可以重新运行jupyter lab --browser=chrome,这应该会启动 Chrome 浏览器。

  1. 如果你不希望系统自动启动浏览器,可以使用以下代码:
$ jupyter lab --no-browser

网络服务器将启动,你可以手动打开任何你偏好的浏览器,并将其指向http://localhost:8888

如果系统要求你输入 token,你可以复制并粘贴终端中显示的带 token 的 URL,格式如下:

To access the server, open this file in a browser:
        file:///Users/tarek.atwan/Library/Jupyter/runtime/jpserver-44086-open.html
    Or copy and paste one of these URLs:
        http://localhost:8888/lab?token=5c3857b9612aecd3
c34e9a40e5eac4509a6ccdbc8a765576
     or http://127.0.0.1:8888/lab?token=5c3857b9612aecd3
c34e9a40e5eac4509a6ccdbc8a765576
  1. 最后,如果默认的port 8888端口正在使用中,或者你希望更改端口号,可以添加-p并指定你想要的端口号,如下例所示。在这里,我指示网络服务器使用port 8890
$ jupyter lab --browser=chrome --port 8890

这将启动 Chrome 并指向localhost:8890/lab

请注意,当 JupyterLab 启动时,你只会在笔记本/控制台部分看到一个内核。这是基础 Python 内核。我们原本预期看到两个内核,分别反映我们拥有的两个环境:基础环境和timeseries虚拟环境。让我们使用以下命令检查我们有多少个虚拟环境:

  1. 下图显示了 JupyterLab 界面,只有一个内核,属于基础环境:

    https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file9.jpg

    图 1.8:JupyterLab 界面,只显示一个内核,该内核属于基础环境

  2. 下图显示了两个 Python 环境:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file10.jpg

图 1.9:显示两个 Python 环境

我们可以看到 timeseries 虚拟环境是活动的。

  1. 你需要为新的timeseries环境安装 Jupyter 内核。首先,关闭网络服务器(即使没有关闭它也可以继续使用)。假设你仍然处于活动的timeseries Python 环境中,只需输入以下命令:
$ python -m ipykernel install --user  --name timeseries --display-name "Time Series"
> Installed kernelspec timeseries in /Users/tarek.atwan/Library/Jupyter/kernels/timeseries
  1. 我们可以使用以下命令检查 Jupyter 可用的内核数量:
$ jupyter kernelspec list

下图显示了创建的kernelspec文件及其位置:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file11.jpg

图 1.10:Jupyter 可用内核列表

这些作为指针,将 GUI 与适当的环境连接,以执行我们的 Python 代码。

  1. 现在,你可以重新启动 JupyterLab,并注意到变化:
$ jupyter lab

启动后将出现以下屏幕:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file12.jpg

图 1.11:现在我们的时间序列内核已在 JupyterLab 中可用

它是如何工作的……

当你创建了新的 timeseries 环境并使用 conda install 安装所需的包时,它会在 envs 文件夹中创建一个新的子文件夹,以隔离该环境及其安装的包与其他环境(包括基础环境)。当从基础环境执行 jupyter notebookjupyter lab 命令时,它需要读取 kernelspec 文件(JSON),以映射到可用的内核,确保它们可以使用。kernelspec 文件可以通过 ipykernel 创建,方法如下:

python -m ipykernel install --user  --name timeseries --display-name "Time Series"

这里,--name 是环境名称,--display-name 是 Jupyter GUI 中的显示名称,可以是你想要的任何名称。现在,任何你在 timeseries 环境中安装的库都可以通过内核从 Jupyter 访问(再次说明,它是 Jupyter GUI 与后端 Python 环境之间的映射)。

还有更多……

JupyterLab 允许你安装多个有用的扩展。这些扩展中有些由 Jupyter 创建和管理,而其他的则由社区创建。

你可以通过两种方式管理 JupyterLab 扩展:通过命令行使用 jupyter labextension install <someExtension> 或通过图形界面使用 扩展管理器。以下截图显示了 Jupyter 扩展管理器 UI 的样子:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file13.jpg

图 1.12:点击 JupyterLab 中的扩展管理器图标

一旦你点击 启用,你将看到可用的 Jupyter 扩展列表。要安装扩展,只需点击 安装 按钮。

一些包需要先安装 Node.js 和 npm,你将看到类似以下的警告:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file14.jpg

图 1.13:当需要 Node.js 和 npm 时,扩展安装错误

你可以直接从 nodejs.org/en/ 下载并安装 Node.js。

另外,你也可以使用 conda 安装 Node.js,方法是使用以下命令:

$ conda install -c conda-forge nodejs

另见

第二章:2 从文件读取时间序列数据

加入我们的 Discord 书籍社区

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file0.png

packt.link/zmkOY

在本章中,我们将使用 pandas,一个流行的 Python 库,具有丰富的 I/O 工具、数据处理和日期/时间功能,用于简化处理 时间序列数据。此外,您还将探索 pandas 中可用的多个读取函数,来导入不同文件类型的数据,如 逗号分隔值 (CSV)、Excel 和 SAS。您将探索如何从文件中读取数据,无论这些文件是存储在本地驱动器上,还是远程存储在云端,如 AWS S3 桶

时间序列数据是复杂的,可能有不同的形状和格式。幸运的是,pandas 的读取函数提供了大量的参数(选项),以帮助处理数据的多样性。

pandas 库提供了两个基本的数据结构:Series 和 DataFrame,它们作为类实现。DataFrame 类是一个用于处理表格数据(类似于电子表格中的行和列)的独特数据结构。它们之间的主要区别在于,Series 是一维的(单列),而 DataFrame 是二维的(多列)。它们之间的关系是,当你从 DataFrame 中切片出一列时,你得到的是一个 Series。你可以将 DataFrame 想象成是两个或多个 Series 对象的并排拼接。

Series 和 DataFrame 数据结构的一个特性是,它们都具有一个叫做索引的标签轴。你在时间序列数据中常见的索引类型是 DatetimeIndex,你将在本章中进一步了解。通常,索引使切片和切割操作变得非常直观。例如,为了使 DataFrame 准备好进行时间序列分析,你将学习如何创建具有 DatetimeIndex 类型索引的 DataFrame。

我们将涵盖以下将数据导入 pandas DataFrame 的方法:

  • 从 CSV 和其他分隔文件读取数据

  • 从 Excel 文件读取数据

  • 从 URL 读取数据

  • 从 Parquet 文件读取数据处理大型数据文件

为什么选择 DATETIMEINDEX?

一个具有 DatetimeIndex 类型索引的 pandas DataFrame 解锁了在处理时间序列数据时所需的大量功能和有用的函数。你可以将其视为为 pandas 增加了一层智能或感知,使其能够将 DataFrame 视为时间序列 DataFrame。

技术要求

在本章及后续章节中,我们将广泛使用 pandas 2.2.0(2024 年 1 月 20 日发布)。

在我们的整个过程中,你将安装其他 Python 库,以便与 pandas 一起使用。你可以从 GitHub 仓库下载 Jupyter 笔记本(github.com/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook./blob/main/code/Ch2/Chapter%202.ipynb)来跟着做。

你可以通过以下链接从 GitHub 仓库下载本章使用的数据集:github.com/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook./tree/main/datasets/Ch2

从 CSV 文件和其他分隔符文件读取数据

在这个示例中,你将使用pandas.read_csv()函数,它提供了一个庞大的参数集,你将探索这些参数以确保数据正确读取到时间序列 DataFrame 中。此外,你将学习如何指定索引列,将索引解析为DatetimeIndex类型,并将包含日期的字符串列解析为datetime对象。

通常,使用 Python 从 CSV 文件读取的数据会是字符串格式(文本)。当使用read_csv方法在 pandas 中读取时,它会尝试推断适当的数据类型(dtype),在大多数情况下,它做得非常好。然而,也有一些情况需要你明确指示哪些列应转换为特定的数据类型。例如,你将使用parse_dates参数指定要解析为日期的列。

准备工作

你将读取一个包含假设电影票房数据的 CSV 文件。该文件已提供在本书的 GitHub 仓库中。数据文件位于datasets/Ch2/movieboxoffice.csv

如何操作…

你将使用 pandas 读取我们的 CSV 文件,并利用read_csv中的一些可用参数:

  1. 首先,加载所需的库:
import pandas as pd
from pathlib import Path
  1. 为文件位置创建一个Path对象:
filepath =\
 Path('../../datasets/Ch2/movieboxoffice.csv')
  1. 使用read_csv函数将 CSV 文件读取到 DataFrame 中,并传递包含额外参数的filepath

CSV 文件的第一列包含电影发布日期,需要将其设置为DatetimeIndex类型的索引(index_col=0parse_dates=['Date'])。通过提供列名列表给usecols来指定你希望包含的列。默认行为是第一行包含表头(header=0):

ts = pd.read_csv(filepath,
                 header=0,
                 parse_dates=['Date'],
                 index_col=0,
                 infer_datetime_format=True,
                 usecols=['Date',
                          'DOW',
                          'Daily',
                          'Forecast',
                          'Percent Diff'])
ts.head(5)

这将输出以下前五行:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file16.png

图 2.1:JupyterLab 中 ts DataFrame 的前五行

  1. 打印 DataFrame 的摘要以检查索引和列的数据类型:
ts.info()
>> <class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 128 entries, 2021-04-26 to 2021-08-31
Data columns (total 4 columns):
 #   Column        Non-Null Count  Dtype
---  ------        --------------  -----
 0   DOW           128 non-null    object
 1   Daily         128 non-null    object
 2   Forecast      128 non-null    object
 3   Percent Diff  128 non-null    object
dtypes: object(4)
memory usage: 5.0+ KB
  1. 注意,Date 列现在是一个索引(而非列),类型为 DatetimeIndex。另外,DailyForecast 列的 dtype 推断错误。你本来期望它们是 float 类型。问题在于源 CSV 文件中的这两列包含了美元符号 ($) 和千位分隔符(,)。这些非数字字符会导致列被解释为字符串。具有 dtypeobject 的列表示该列包含字符串或混合类型的数据(不是同质的)。

要解决这个问题,你需要去除美元符号 ($) 和千位分隔符(,)或任何其他非数字字符。你可以使用 str.replace() 来完成此操作,它可以接受正则表达式来移除所有非数字字符,但排除小数点(.)。移除这些字符不会转换 dtype,因此你需要使用 .astype(float) 将这两列转换为 float 类型:

clean = lambda x: x.str.replace('[^\\d]','', regex=True)
c_df = ts[['Daily', 'Forecast']].apply(clean, axis=1)
ts[['Daily', 'Forecast']] = c_df.astype(float)

打印更新后的 DataFrame 摘要:

ts.info()
>> <class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 128 entries, 2021-04-26 to 2021-08-31
Data columns (total 4 columns):
 #   Column        Non-Null Count  Dtype
---  ------        --------------  -----
 0   DOW           128 non-null    object
 1   Daily         128 non-null    float64
 2   Forecast      128 non-null    float64
 3   Percent Diff  128 non-null    object
dtypes: float64(2), object(2)
memory usage: 5.0+ KB

现在,你拥有一个 DatetimeIndex 的 DataFrame,并且 DailyForecast 列的 dtype 都是 float64(数字类型)。

它是如何工作的……

使用 pandas 进行数据转换非常快速,因为它将数据加载到内存中。例如,read_csv 方法会读取并将整个数据加载到内存中的 DataFrame 中。当使用 info() 方法请求 DataFrame 的摘要时,输出除了显示列和索引的数据类型外,还会显示整个 DataFrame 的内存使用情况。要获取每个列的确切内存使用情况,包括索引,你可以使用 memory_usage() 方法:

ts.memory_usage()
>>
Index           1024
DOW             1024
Daily           1024
Forecast        1024
Percent Diff    1024
dtype: int64

总计将与 DataFrame 摘要中提供的内容匹配:

ts.memory_usage().sum()
>> 5120

到目前为止,你在使用 read_csv 读取 CSV 文件时,已经使用了一些可用的参数。你对 pandas 阅读函数中不同选项越熟悉,你在数据读取(导入)过程中就能做更多的前期预处理工作。

你使用了内建的 parse_dates 参数,它接收一个列名(或位置)列表。将 index_col=0parse_dates=[0] 组合在一起,生成了一个具有 DatetimeIndex 类型的索引的 DataFrame。

让我们查看官方 pandas.read_csv() 文档中定义的本示例中使用的参数(pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html):

  • filepath_or_buffer:这是第一个位置参数,也是读取 CSV 文件时所需的唯一必填字段。这里,你传递了一个名为 filepath 的 Python 路径对象。它也可以是一个表示有效文件路径的字符串,例如 '../../datasets/Ch2/movieboxoffice.csv',或者指向远程文件位置的 URL,例如 AWS S3 存储桶(我们将在本章的 从 URL 读取数据 示例中进一步探讨)。

  • sep:该参数用于指定分隔符的字符串。默认分隔符是逗号(,),假设是一个 CSV 文件。如果文件使用其他分隔符,例如管道符号(|)或分号(;),可以更新该参数,例如sep="|"sep=";"

  • sep的另一个别名是delimiter,也可以作为参数名使用。

  • header:在这种情况下,你指定了第一行(0)包含表头信息。默认值是infer,通常在大多数情况下可以直接使用。如果 CSV 文件没有表头,则需要指定header=None。如果 CSV 文件有表头,但你希望提供自定义的列名,则需要指定header=0并通过names参数提供新的列名列表来覆盖它。

  • parse_dates:在本示例中,你提供了列位置的列表[0],这表示仅解析第一列(按位置)。parse_dates参数可以接受列名的列表,例如["Date"],或者列位置的列表,例如[0, 3],表示第一列和第四列。如果你仅打算解析index_col参数中指定的索引列,只需传递True(布尔值)。

  • index_col:你指定了第一列的位置(index_col=0)作为 DataFrame 的索引。或者,你也可以提供列名作为字符串(index_col='Date')。该参数还可以接受一个整数列表(位置索引)或字符串列表(列名),这将创建一个MultiIndex对象。

  • usecols:默认值为None,表示包含数据集中的所有列。限制列的数量,仅保留必要的列可以加快解析速度,并减少内存使用,因为只引入了需要的数据。usecols参数可以接受一个列名的列表,例如['Date', 'DOW', 'Daily', 'Percent Diff', 'Forecast'],或者一个位置索引的列表,例如[0, 1, 3, 7, 6],两者会产生相同的结果。

回想一下,你通过将列名列表传递给usecols参数,指定了要包含的列。这些列名是基于文件头部(CSV 文件的第一行)。

如果你决定提供自定义的列名,则无法在usecols参数中引用原始列名;这会导致以下错误:ValueError: Usecols do not match columns.

还有更多内容……

有些情况下,parse_dates可能无法正常工作(即无法解析日期)。在这种情况下,相关列将保持原样,并且不会抛出错误。这时,date_format参数可以派上用场。

以下代码展示了如何使用date_format

ts = pd.read_csv(filepath,
                 parse_dates=[0],
                 index_col=0,
                 date_format="%d-%b-%Y",
                 usecols=[0,1,3, 7, 6])
ts.head()

上述代码将打印出ts DataFrame 的前五行,正确地展示解析后的Date索引。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file17.jpg

图 2.2:使用 JupyterLab 查看 ts DataFrame 的前五行

让我们分解一下。在上面的代码中,由于日期以 26-Apr-2021 这样的字符串形式存储,你传递了 "%d-%b-%Y" 来反映这一点:

  • %d 表示月份中的日期,例如 0102

  • %b 表示缩写的月份名称,例如 AprMay

  • %Y 表示四位数的年份,例如 20202021

其他常见的字符串代码包括以下内容:

  • %y 表示两位数的年份,例如 1920

  • %B 表示月份的全名,例如 JanuaryFebruary

  • %m 表示月份,作为两位数,例如 0102

有关 Python 字符串格式用于表示日期的更多信息,请访问 strftime.org

另见

处理更复杂的日期格式时,另一个选项是使用 to_datetime() 函数。to_datetime() 函数用于将字符串、整数或浮动数值转换为日期时间对象。

最初,你将按原样读取 CSV 数据,然后应用 to_datetime() 函数将特定列解析为所需的日期时间格式。以下代码展示了这一过程:

ts = pd.read_csv(filepath,
                 index_col=0,
                 usecols=[0,1,3, 7, 6])
ts.index = pd.to_datetime(ts.index, format="%d-%b-%Y")

最后一行,ts.index = pd.to_datetime(ts.index, format="%d-%b-%Y"),ts 数据框的索引转换为 DatetimeIndex 对象。请注意,我们如何指定数据字符串格式,类似于在 还有更多… 部分的 read_csv() 函数中使用 date_format 参数的方式。

从 Excel 文件中读取数据

要从 Excel 文件中读取数据,你需要使用 pandas 提供的不同读取函数。一般来说,处理 Excel 文件可能会有些挑战,因为文件可能包含格式化的多行标题、合并的标题单元格以及图片。它们还可能包含多个工作表,每个工作表都有自定义的名称(标签)。因此,在操作 Excel 文件之前,务必先检查文件。最常见的场景是读取包含多个工作表的 Excel 文件,这也是本教程的重点。

在这个教程中,你将使用 pandas.read_excel() 函数,并检查可用的各种参数,以确保数据作为具有 DatetimeIndex 的 DataFrame 正确读取,用于时间序列分析。此外,你还将探索读取包含多个工作表的 Excel 文件的不同选项。

准备工作

要使用 pandas.read_excel(),你需要安装额外的库来读取和写入 Excel 文件。在 read_excel() 函数中,你将使用 engine 参数指定处理 Excel 文件所需的库(引擎)。根据你所处理的 Excel 文件扩展名(例如 .xls.xlsx),你可能需要指定不同的引擎,这可能需要安装额外的库。

支持读取和写入 Excel 的库(引擎)包括 xlrdopenpyxlodfpyxlsb。处理 Excel 文件时,最常用的两个库通常是 xlrdopenpyxl

xlrd 库只支持 .xls 文件。因此,如果你正在处理较旧的 Excel 格式,例如 .xls,那么 xlrd 就能很好地工作。对于更新的 Excel 格式,例如 .xlsx,我们需要使用不同的引擎,在这种情况下,推荐使用 openpyxl

要使用 conda 安装 openpyxl,请在终端运行以下命令:

>>> conda install openpyxl

要使用 pip 安装,请运行以下命令:

>>> pip install openpyxl

我们将使用 sales_trx_data.xlsx 文件,你可以从本书的 GitHub 仓库下载。请参阅本章的 技术要求 部分。该文件包含按年份拆分的销售数据,分别存在两个工作表中(20172018)。

如何操作…

你将使用 pandas 和 openpyxl 导入 Excel 文件(.xlsx),并利用 read_excel() 中的一些可用参数:

  1. 导入此配方所需的库:
import pandas as pd
from pathlib import Path
filepath = \
Path('../../datasets/Ch2/sales_trx_data.xlsx')
  1. 使用 read_excel() 函数读取 Excel(.xlxs)文件。默认情况下,pandas 只读取第一个工作表。这个参数在 sheet_name 中指定,默认值设置为 0。在传递新的参数之前,你可以先使用 pandas.ExcelFile 来检查文件并确定可用工作表的数量。ExcelFile 类将提供额外的方法和属性,例如 sheet_name,它返回一个工作表名称的列表:
excelfile = pd.ExcelFile(filepath)
excelfile.sheet_names
>> ['2017', '2018']

如果你有多个工作表,可以通过将一个列表传递给 read_excel 中的 sheet_name 参数来指定要导入的工作表。该列表可以是位置参数,如第一个、第二个和第五个工作表 [0, 1, 4],工作表名称 ["Sheet1", "Sheet2", "Sheet5"],或两者的组合,例如第一个工作表、第二个工作表和一个名为 "Revenue" 的工作表 [0, 1, "Revenue"]

在以下代码中,你将使用工作表位置来读取第一个和第二个工作表(01 索引)。这将返回一个 Python dictionary 对象,包含两个 DataFrame。请注意,返回的字典(键值对)具有数字键(01),分别表示第一个和第二个工作表(位置索引):

ts = pd.read_excel(filepath,
                   engine='openpyxl',
                   index_col=1,
                   sheet_name=[0,1],
                   parse_dates=True)
ts.keys()
>> dict_keys([0, 1])
  1. 或者,你可以传递一个工作表名称的列表。请注意,返回的字典键现在是字符串,表示工作表名称,如以下代码所示:
ts = pd.read_excel(filepath,
                   engine='openpyxl',
                   index_col=1,
                   sheet_name=['2017','2018'],
                   parse_dates=True)
ts.keys()
>> dict_keys(['2017', '2018'])
  1. 如果你想从所有可用工作表中读取数据,可以传递 None。在这种情况下,字典的键将表示工作表名称:
ts = pd.read_excel(filepath,
                   engine='openpyxl',
                   index_col=1,
                   sheet_name=None,
                   parse_dates=True)
ts.keys()
>> dict_keys(['2017', '2018'])

字典中的两个 DataFrame 在它们的架构(列名和数据类型)上是相同的(同类型)。你可以通过 ts['2017'].info()ts['2018'].info() 来检查每个 DataFrame。

它们都有一个 DatetimeIndex 对象,你在 index_col 参数中指定了该对象。2017 年的 DataFrame 包含 36,764 行,2018 年的 DataFrame 包含 37,360 行。在这种情况下,你希望将两个 DataFrame 堆叠(合并)(类似于 SQL 中的 UNION),得到一个包含所有 74,124 行且 DatetimeIndex2017-01-012018-12-31 的单一 DataFrame。

要沿着索引轴(一个接一个堆叠)将两个 DataFrame 合并,你将使用 pandas.concat() 函数。concat() 函数的默认行为是沿着索引轴连接(axis=0)。在以下代码中,你将明确指定要连接哪些 DataFrame:

ts_combined = pd.concat([ts['2017'],ts['2018']])
ts_combined.info()
>> <class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 74124 entries, 2017-01-01 to 2018-12-31
Data columns (total 4 columns):
 #   Column              Non-Null Count  Dtype
---  ------              --------------  -----
 0   Line_Item_ID        74124 non-null  int64
 1   Credit_Card_Number  74124 non-null  int64
 2   Quantity            74124 non-null  int64
 3   Menu_Item           74124 non-null  object
dtypes: int64(3), object(1)
memory usage: 2.8+ MB
  1. 当返回多个 DataFrame 时(比如多个工作表),你可以对返回的字典使用 concat() 函数。换句话说,你可以在一个语句中将 concat()read_excel() 函数结合使用。在这种情况下,最终你会得到一个 MultiIndex DataFrame,其中第一级是工作表名称(或编号),第二级是 DatetimeIndex。例如,使用 ts 字典,你会得到一个两级索引:MultiIndex([('2017', '2017-01-01'), ..., ('2018', '2018-12-31')], names=[None, 'Date'], length=74124)

要减少级别数,你可以使用 droplevel(level=0) 方法,在 pandas .concat() 之后删除第一级,示例如下:

ts_combined = pd.concat(ts).droplevel(level=0)
  1. 如果你只读取一个工作表,行为会略有不同。默认情况下,sheet_name 被设置为 0,这意味着它读取第一个工作表。你可以修改这个设置并传递一个不同的值(单一值),无论是工作表名称(字符串)还是工作表位置(整数)。当传递单一值时,返回的对象将是一个 pandas DataFrame,而不是字典:
ts = pd.read_excel(filepath,
                   index_col=1,
                   sheet_name='2018',
                   parse_dates=True)
type(ts)
>> pandas.core.frame.DataFrame

但请注意,如果你在两个括号内传递一个单一值([1]),那么 pandas 会以不同的方式解释它,返回的对象将是一个包含一个 DataFrame 的字典。

最后,请注意,在最后一个示例中你不需要指定引擎。read_csv 函数将根据文件扩展名确定使用哪个引擎。所以,假设该引擎的库没有安装,在这种情况下,它会抛出一个 ImportError 消息,指出缺少该库(依赖项)。

工作原理……

pandas.read_excel() 函数与之前使用过的 pandas.read_csv() 函数有许多相同的常见参数。read_excel 函数可以返回一个 DataFrame 对象或一个包含 DataFrame 的字典。这里的依赖关系在于你是传递一个单一值(标量)还是一个列表给 sheet_name

sales_trx_data.xlsx文件中,两个工作表具有相同的架构(同质类型)。销售数据按年份进行分区(拆分),每个工作表包含特定年份的销售数据。在这种情况下,连接这两个 DataFrame 是一个自然的选择。pandas.concat()函数类似于DataFrame.append()函数,其中第二个 DataFrame 被添加(附加)到第一个 DataFrame 的末尾。对于来自 SQL 背景的用户来说,这应该类似于UNION子句的行为。

还有更多…

另一种读取 Excel 文件的方法是使用pandas.ExcelFile()类,它返回一个 pandas ExcelFile对象。在本食谱的早些时候,您使用ExcelFile()通过sheet_name属性检查 Excel 文件中的工作表数量。

ExcelFile类具有多个有用的方法,包括parse()方法,用于将 Excel 文件解析为 DataFrame,类似于pandas.read_excel()函数。

在下面的示例中,您将使用ExcelFile类解析第一个工作表,将第一列作为索引,并打印前五行:

excelfile = pd.ExcelFile(filepath)
excelfile.parse(sheet_name='2017',
                index_col=1,
                parse_dates=True).head()

您应该会看到类似的结果,显示数据框(DataFrame)的前五行:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file18.jpg

图 2.3:使用 JupyterLab 显示数据框的前五行

图 2.3中,应该能清楚地看出,ExcelFile.parse()相当于pandas.read_excel()

另请参见

有关pandas.read_excel()pandas.ExcelFile()的更多信息,请参考官方文档:

从 URL 读取数据

文件可以下载并存储在本地计算机上,或存储在远程服务器或云端位置。在前两个示例中,从 CSV 和其他分隔文件读取从 Excel 文件读取数据,两个文件都存储在本地。

pandas 的许多读取函数可以通过传递 URL 路径从远程位置读取数据。例如,read_csv()read_excel()可以接受一个 URL 来读取通过互联网访问的文件。在本例中,您将使用pandas.read_csv()读取 CSV 文件,使用pandas.read_excel()读取 Excel 文件,数据源来自远程位置,如 GitHub 和 AWS S3(私有和公共桶)。您还将直接从 HTML 页面读取数据并导入到 pandas DataFrame 中。

准备工作

您需要安装AWS SDK for PythonBoto3),以便从 S3 桶读取文件。此外,您还将学习如何使用storage_options参数,它在 pandas 中的许多读取函数中可用,用于在没有 Boto3 库的情况下从 S3 读取数据。

要在 pandas 中使用 S3 URL(例如,s3://bucket_name/path-to-file),您需要安装 s3fs 库。您还需要安装一个 HTML 解析器,当我们使用 read_html() 时。比如,解析引擎(HTML 解析器)可以选择安装 lxmlhtml5lib;pandas 会选择安装的解析器(它会首先查找 lxml,如果失败,则查找 html5lib)。如果您计划使用 html5lib,则需要安装 Beautiful Soup(beautifulsoup4)。

使用 pip 安装,您可以使用以下命令:

>>> pip install boto3 s3fs lxml html5lib

使用 Conda 安装,您可以使用:

>>> conda install boto3 s3fs lxml html5lib -y

如何操作…

本节将向您展示从在线(远程)源读取数据时的不同场景。让我们先导入 pandas,因为在整个本节中都会使用它:

import pandas as pd
从 GitHub 读取数据

有时,您可能会在 GitHub 上找到有用的公共数据,希望直接使用并读取(而不是下载)。GitHub 上最常见的文件格式之一是 CSV 文件。让我们从以下步骤开始:

  1. 要从 GitHub 读取 CSV 文件,您需要获取原始内容的 URL。如果您从浏览器复制文件的 GitHub URL 并将其作为文件路径使用,您将得到一个如下所示的 URL:github.com/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook./blob/main/datasets/Ch2/AirQualityUCI.csv。此 URL 是指向 GitHub 网页,而不是数据本身;因此,当使用 pd.read_csv() 时,它会抛出错误:
url = 'https://github.com/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook./blob/main/datasets/Ch2/AirQualityUCI.csv'
pd.read_csv(url)
ParserError: Error tokenizing data. C error: Expected 1 fields in line 62, saw 2
  1. 相反,您需要原始内容,这会给您一个如下所示的 URL:raw.githubusercontent.com/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook./main/datasets/Ch2/AirQualityUCI.csv

    https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file19.jpg

    图 2.4:CSV 文件的 GitHub 页面。注意查看原始按钮

  2. 图 2.4 中,请注意值没有用逗号分隔(不是逗号分隔文件);相反,文件使用分号(;)来分隔值。

文件中的第一列是 Date 列。您需要解析(使用 parse_date 参数)并将其转换为 DatetimeIndexindex_col 参数)。

将新的 URL 传递给 pandas.read_csv()

url = 'https://media.githubusercontent.com/media/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook./main/datasets/Ch2/AirQualityUCI.csv'
df = pd.read_csv(url,
                 delimiter=';',
                 parse_dates=['Date'],
                 index_col='Date')
df.iloc[:3,1:4]
>>
              CO(GT)  PT08.S1(CO)  NMHC(GT)
Date
10/03/2004 2.6 1360.00 150
10/03/2004  2.0 1292.25 112
10/03/2004  2.2 1402.00 88

我们成功地将 GitHub 上的 CSV 文件数据导入到 DataFrame 中,并打印了选定列的前三行数据。

从公共 S3 存储桶读取数据

AWS 支持 虚拟主机风格 的 URL,如 https://bucket-name.s3.Region.amazonaws.com/keyname路径风格 的 URL,如 https://s3.Region.amazonaws.com/bucket-name/keyname,以及使用 S3://bucket/keyname。以下是这些不同 URL 在我们文件中的示例:

在此示例中,您将读取 AirQualityUCI.xlsx 文件,该文件只有一个工作表,包含与先前从 GitHub 读取的 AirQualityUCI.csv 相同的数据。

请注意,在 URL 中,您不需要指定 us-east-1 区域。us-east-1 区域代表美国东部(北弗吉尼亚),是一个 例外。其他区域则不是这种情况:

url = 'https://tscookbook.s3.amazonaws.com/AirQualityUCI.xlsx'
df = pd.read_excel(url,
                   index_col='Date',
                   parse_dates=True)

使用 S3:// URL 读取相同的文件:

s3uri = 's3://tscookbook/AirQualityUCI.xlsx'
df = pd.read_excel(s3uri,
                   index_col='Date',
                   parse_dates=True)

您可能会遇到如下错误:

ImportError: Install s3fs to access S3

这表明您要么没有安装 s3fs 库,要么可能没有使用正确的 Python/Conda 环境。

从私有 S3 存储桶读取数据

从私有 S3 存储桶读取文件时,您需要传递凭证以进行身份验证。pandas 中许多 I/O 函数中的一个便捷参数是 storage_options,它允许您在请求中发送额外的内容,例如自定义头部或所需的云服务凭证。

您需要传递一个字典(键值对),以便与请求一起提供额外的信息,例如用户名、密码、访问密钥和密钥访问密钥,传递给 storage_options,如 {"username": username, "password": password}

现在,您将读取位于私有 S3 存储桶中的 AirQualityUCI.csv 文件:

  1. 您将从将您的 AWS 凭证存储在 Python 脚本之外的 .cfg 配置文件开始。然后,使用 configparser 读取这些值,并将其存储在 Python 变量中。您不希望凭证暴露或硬编码在代码中:
# Example aws.cfg file
[AWS]
aws_access_key=your_access_key
aws_secret_key=your_secret_key

您可以使用 config.read() 加载 aws.cfg 文件:

import configparser
config = configparser.ConfigParser()
config.read('aws.cfg')
AWS_ACCESS_KEY = config['AWS']['aws_access_key']
AWS_SECRET_KEY = config['AWS']['aws_secret_key']
  1. AWS 访问密钥 ID密钥访问密钥 现在存储在 AWS_ACCESS_KEYAWS_SECRET_KEY 中。使用 pandas.read_csv() 读取 CSV 文件,并通过传递您的凭证来更新 storage_options 参数,如下代码所示:
s3uri = "s3://tscookbook-private/AirQuality.csv"
df = pd.read_csv(s3uri,
                 index_col='Date',
                 parse_dates=True,
                 storage_options= {
                         'key': AWS_ACCESS_KEY,
                         'secret': AWS_SECRET_KEY
                     })
df.iloc[:3, 1:4]
>>
           CO(GT)  PT08.S1(CO)  NMHC(GT)
Date
2004-10-03      2.6       1360.0     150.0
2004-10-03      2.0       1292.0     112.0
2004-10-03      2.2       1402.0      88.0
  1. 或者,您可以使用 AWS 的 Python SDK(Boto3)来实现类似的功能。boto3 Python 库为您提供了更多的控制和额外的功能(不仅仅是读取和写入 S3)。您将传递之前存储在 AWS_ACCESS_KEYAWS_SECRET_KEY 中的凭证,并通过 boto3 进行身份验证:
import boto3
bucket = "tscookbook-private"
client = boto3.client("s3",
                  aws_access_key_id =AWS_ACCESS_KEY,
                  aws_secret_access_key = AWS_SECRET_KEY)

现在,client 对象可以访问许多特定于 AWS S3 服务的方法,用于创建、删除和检索存储桶信息等。此外,Boto3 提供了两种级别的 API:客户端和资源。在前面的示例中,您使用了客户端 API。

客户端是一个低级服务访问接口,提供更精细的控制,例如,boto3.client("s3")。资源是一个更高级的面向对象接口(抽象层),例如,boto3.resource("s3")

第四章将时间序列数据持久化到文件 中,您将探索写入 S3 时的 resource API 接口。目前,您将使用客户端接口。

  1. 您将使用 get_object 方法来检索数据。只需提供存储桶名称和密钥。这里的密钥是实际的文件名:
data = client.get_object(Bucket=bucket, Key='AirQuality.csv')
df = pd.read_csv(data['Body'],
                 index_col='Date',
                 parse_dates=True)

df.iloc[:3, 1:4]
>>
           CO(GT)  PT08.S1(CO)  NMHC(GT)
Date
2004-10-03    2,6       1360.0     150.0
2004-10-03      2       1292.0     112.0
2004-10-03    2,2       1402.0      88.0

调用 client.get_object() 方法时,将返回一个字典(键值对),如以下示例所示:

{'ResponseMetadata': {
'RequestId':'MM0CR3XX5QFBQTSG',
'HostId':'vq8iRCJfuA4eWPgHBGhdjir1x52Tdp80ADaSxWrL4Xzsr
VpebSZ6SnskPeYNKCOd/RZfIRT4xIM=',
'HTTPStatusCode':200,
'HTTPHeaders': {'x-amz-id-2': 'vq8iRCJfuA4eWPgHBGhdjir1x52
Tdp80ADaSxWrL4XzsrVpebSZ6SnskPeYNKCOd/RZfIRT4xIM=',
   'x-amz-request-id': 'MM0CR3XX5QFBQTSG',
   'date': 'Tue, 06 Jul 2021 01:08:36 GMT',
   'last-modified': 'Mon, 14 Jun 2021 01:13:05 GMT',
   'etag': '"2ce337accfeb2dbbc6b76833bc6f84b8"',
   'accept-ranges': 'bytes',
   'content-type': 'binary/octet-stream',
   'server': 'AmazonS3',
   'content-length': '1012427'},
   'RetryAttempts': 0},
   'AcceptRanges': 'bytes',
 'LastModified': datetime.datetime(2021, 6, 14, 1, 13, 5, tzinfo=tzutc()),
 'ContentLength': 1012427,
 'ETag': '"2ce337accfeb2dbbc6b76833bc6f84b8"',
 'ContentType': 'binary/octet-stream',
 'Metadata': {},
 'Body': <botocore.response.StreamingBody at 0x7fe9c16b55b0>}

你感兴趣的内容在响应体中的 Body 键下。你将 data['Body'] 传递给 read_csv() 函数,它会将响应流(StreamingBody)加载到 DataFrame 中。

从 HTML 中读取数据

pandas 提供了一种优雅的方式来读取 HTML 表格并使用 pandas.read_html() 函数将内容转换为 pandas DataFrame:

  1. 在以下示例中,我们将从 Wikipedia 提取 HTML 表格,用于按国家和地区跟踪 COVID-19 大流行病例(en.wikipedia.org/wiki/COVID-19_pandemic_by_country_and_territory):
url = "https://en.wikipedia.org/wiki/COVID-19_pandemic_by_country_and_territory"
results = pd.read_html(url)
print(len(results))
>>
  1. pandas.read_html() 返回一个包含 DataFrame 的列表,每个 HTML 表格对应一个 DataFrame,位于 URL 中找到的每个 HTML 表格。请注意,网站内容是动态的,且会定期更新,因此结果可能会有所不同。在我们的例子中,返回了 69 个 DataFrame。索引为 15 的 DataFrame 包含按地区划分的 COVID-19 病例和死亡情况的汇总。获取该 DataFrame(位于索引 15)并将其分配给 df 变量,接着打印返回的列:
df = results[15]
df.columns
>>
Index(['Region[30]', 'Total cases', 'Total deaths', 'Cases per million',
       'Deaths per million', 'Current weekly cases', 'Current weekly deaths',
       'Population millions', 'Vaccinated %[31]'],
      dtype='object')
  1. 显示 Total casesTotal deathsCases per million 列的前五行:
df[['Region[30]','Total cases', 'Total deaths', 'Cases per million']].head(3)
>>
Region[30]      Total cases Total deaths    Cases per million
0   European Union  179537758   1185108 401363
1   North America   103783777   1133607 281404
2   Other Europe        57721948    498259      247054

它是如何工作的……

大多数 pandas 读取器函数都接受 URL 作为路径。以下是一些示例:

  • pandas.read_csv()

  • pandas.read_excel()

  • pandas.read_parquet()

  • pandas.read_table()

  • pandas.read_pickle()

  • pandas.read_orc()

  • pandas.read_stata()

  • pandas.read_sas()

  • pandas.read_json()

URL 需要是 pandas 支持的有效 URL 方案之一,包括 httphttpsftps3gs,或者 file 协议。

read_html() 函数非常适合抓取包含 HTML 表格数据的网站。它检查 HTML 并搜索其中的所有 <table> 元素。在 HTML 中,表格行使用 <tr> </tr> 标签定义,表头使用 <th></th> 标签定义。实际数据(单元格)包含在 <td> </td> 标签中。read_html() 函数查找 <table><tr><th><td> 标签,并将内容转换为 DataFrame,并根据 HTML 中的定义分配列和行。如果一个 HTML 页面包含多个 <table></table> 标签,read_html 会返回所有的表格,并且你将得到一个 DataFrame 列表。

以下代码演示了pandas.read_html()的工作原理:

from io import StringIO
import pandas as pd
html = """
 <table>
   <tr>
     <th>Ticker</th>
     <th>Price</th>
   </tr>
   <tr>
     <td>MSFT</td>
     <td>230</td>
   </tr>
   <tr>
     <td>APPL</td>
     <td>300</td>
   </tr>
     <tr>
     <td>MSTR</td>
     <td>120</td>
   </tr>
 </table>
 </body>
 </html>
 """

df = pd.read_html(StringIO(html))
df[0]
>>
  Ticker  Price
0   MSFT    230
1   APPL    300
2   MSTR    120

传递 HTML 字面字符串

从 pandas 版本 2.1.0 开始,你需要将 HTML 代码包装在io.StringIO中。StringIO(<HTML CODE>)将从 HTML 字符串创建一个内存中的类文件对象,可以直接传递给read_html()函数。

在前面的代码中,read_html()函数从类文件对象读取 HTML 内容,并将用“<table> … </table>”标签表示的 HTML 表格转换为 pandas 数据框。<th></th>标签之间的内容表示数据框的列名,<tr><td></td></tr>标签之间的内容表示数据框的行数据。请注意,如果你删除<table></table>标签,你将遇到ValueError: No tables found错误。

还有更多…

read_html()函数有一个可选的attr参数,它接受一个包含有效 HTML<table>属性的字典,例如idclass。例如,你可以使用attr参数来缩小返回的表格范围,仅限那些匹配class属性sortable的表格,如<table class="sortable">read_html函数将检查整个 HTML 页面,确保你定位到正确的属性集。

在前一个练习中,你使用了read_html函数在 COVID-19 维基百科页面上,它返回了 71 个表格(数据框)。随着维基百科的更新,表格数量可能会随着时间的推移增加。你可以通过使用attr选项来缩小结果集并保证一定的一致性。首先,使用浏览器检查 HTML 代码。你会看到一些<table>元素有多个类,如sortable。你可以寻找其他独特的标识符。

<table class="wikitable sortable mw-datatable covid19-countrynames jquery-tablesorter" id="thetable" style="text-align:right;">

请注意,如果你收到html5lib not found的错误,请安装它,你需要同时安装html5libbeautifulSoup4

使用conda安装,使用以下命令:

conda install html5lib beautifulSoup4

使用pip安装,使用以下命令:

pip install html5lib beautifulSoup4

现在,让我们使用sortable类并重新请求数据:

url = "https://en.wikipedia.org/wiki/COVID-19_pandemic_by_country_and_territory"
df = pd.read_html(url, attrs={'class': 'sortable'})
len(df)
>>  7
df[3].columns
>>
Index(['Region[28]', 'Total cases', 'Total deaths', 'Cases per million',
       'Deaths per million', 'Current weekly cases', 'Current weekly deaths',
       'Population millions', 'Vaccinated %[29]'],
      dtype='object')

返回的列表是一个较小的表格子集(从71减少到7)。

另见

如需更多信息,请参考官方的pandas.read_html文档:pandas.pydata.org/docs/reference/api/pandas.read_html.html

从 Parquet 文件读取数据

Parquet文件已成为在数据工程和大数据分析领域中高效存储和处理大数据集的热门选择。最初由 Twitter 和 Cloudera 开发,Parquet 后来作为开源列式文件格式贡献给了Apache 基金会。Parquet 的重点是优先考虑快速的数据检索和高效的压缩。它的设计专门针对分析型工作负载,并且作为数据分区的优秀选择,你将在本食谱中以及第四章将时间序列数据持久化到文件中再次探索它。因此,Parquet 已成为现代数据架构和云存储解决方案的事实标准。

在本食谱中,你将学习如何使用 pandas 读取 parquet 文件,并学习如何查询特定分区以实现高效的数据检索。

准备工作

你将读取包含来自美国国家海洋和大气管理局的洛杉矶机场站点天气数据的 parquet 文件,这些文件可以在datasets/Ch2/ LA_weather.parquet文件夹中找到。它包含 2010-2023 年的天气读数,并按年份进行分区(14 个子文件夹)。

你将使用pandas.read_parquet()函数,这需要你安装一个 Parquet 引擎来处理文件。你可以安装fastparquetPyArrow,后者是 pandas 的默认选择。

使用conda安装 PyArrow,运行以下命令:

conda install -c conda-forge pyarrow

使用pip安装 PyArrow,运行以下命令:

pip install pyarrow

如何操作…

PyArrow库允许你将额外的参数(**kwargs)传递给pandas.read_parquet()函数,从而在读取文件时提供更多的选项,正如你将要探索的那样。

读取所有分区

以下步骤用于一次性读取LA_weather.parquet文件夹中的所有分区:

  1. 创建一个路径来引用包含分区的 Parquet 文件夹,并将其传递给read_parquet函数。
file = Path('../../datasets/Ch2/LA_weather.parquet/')
df = pd.read_parquet(file,
                    engine='pyarrow')
  1. 你可以使用.info()方法验证和检查模式
df.info()

这应该会产生以下输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4899 entries, 0 to 4898
Data columns (total 17 columns):
 #   Column           Non-Null Count  Dtype        
---  ------           --------------  -----        
 0   STATION          4899 non-null   object       
 1   NAME             4899 non-null   object       
 2   DATE             4899 non-null   object       
 3   PRCP             4899 non-null   float64      
 4   PRCP_ATTRIBUTES  4899 non-null   object       
 5   SNOW             121 non-null    float64      
 6   SNOW_ATTRIBUTES  121 non-null    object       
 7   SNWD             59 non-null     float64      
 8   SNWD_ATTRIBUTES  59 non-null     object       
 9   TAVG             3713 non-null   float64      
 10  TAVG_ATTRIBUTES  3713 non-null   object       
 11  TMAX             4899 non-null   int64        
 12  TMAX_ATTRIBUTES  4899 non-null   object       
 13  TMIN             4899 non-null   int64        
 14  TMIN_ATTRIBUTES  4899 non-null   object       
 15  DT               4899 non-null   datetime64[ns]
 16  year             4899 non-null   category     
dtypes: category(1), datetime64ns, float64(4), int64(2), object(9)
memory usage: 617.8+ KB
读取特定分区

以下步骤说明如何使用filters参数读取特定分区或分区集,并使用 PyArrow 库中的columns参数指定列:

  1. 由于数据按年份进行分区,你可以使用filters参数来指定特定的分区。在下面的示例中,你将只读取 2012 年的分区:
filters = [('year', '==', 2012)]
df_2012 = pd.read_parquet(file,
                          engine='pyarrow',
                          filters=filters)
  1. 要读取一组分区,例如 2021 年、2022 年和 2023 年的数据,你可以使用以下任意选项,这些选项会产生类似的结果:
filters = [('year', '>', 2020)]
filters = [('year', '>=', 2021)]
filters = [('year', 'in', [2021, 2022, 2023])]

在定义filters对象后,你可以将其分配给read_parquet()函数中的filters参数,如下所示:

df = pd.read_parquet(file,
                     engine='pyarrow',
                     filters=filters)
  1. 另一个有用的参数是columns,它允许你指定要检索的列名,作为 Python 列表传递。
columns = ['DATE', 'year', 'TMAX']
df = pd.read_parquet(file,
                     engine='pyarrow',
                     filters=filters,
                     columns=columns)

在上面的代码中,read_parquet() 函数将仅检索 Parquet 文件中的指定列(‘Date’、‘year’ 和 ‘TMAX’),并使用定义的过滤器。你可以通过运行 df.head()df.info() 来验证结果。

df.head()
>>
DATE        year    TMAX
0   2021-01-01  2021    67
1   2021-01-02  2021    63
2   2021-01-03  2021    62
3   2021-01-04  2021    59
4   2021-01-05  2021    57
df.info()
>>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 881 entries, 0 to 880
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   DATE    881 non-null    object 
 1   year    881 non-null    category
 2   TMAX    881 non-null    int64  
dtypes: category(1), int64(1), object(1)
memory usage: 15.2+ KB

注意,通过指定数据分析所需的分区和列,缩小数据选择范围后,内存使用显著减少。

如何运作…

使用 Parquet 文件格式有多个优势,尤其是在处理大型数据文件时。Parquet 的列式格式提供了更快的数据检索和高效的压缩,使其非常适合云存储,并有助于减少存储成本。Parquet 采用了先进的数据编码技术和算法,从而提高了压缩率。

以下图 2.5 显示了一个按年份划分的 Parquet 数据集文件夹结构示例。每个年份都有自己的子文件夹,在每个子文件夹内,包含有单独的文件。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file20.png

图 2.5:按年份划分的 Parquet 数据集文件夹结构示例

Parquet 文件被称为“自描述”文件,因为每个文件包含编码的数据和位于页脚部分的附加元数据。元数据包括 Parquet 格式的版本、数据模式和结构(例如列类型),以及其他统计信息,如列的最小值和最大值。因此,在使用 pandas 读写 Parquet 数据集时,你会注意到 DataFrame 的模式得以保留。

根据官方 pandas 文档,你需要熟悉一些关键参数,这些文档可在其官网页面中找到(pandas.pydata.org/docs/reference/api/pandas.read_parquet.html)。你已经在之前的 如何操作… 部分使用了其中一些参数。以下是你已经使用过的关键参数:

read_parquet(
    path: 'FilePath | ReadBuffer[bytes]',
    engine: 'str' = 'auto',
    columns: 'list[str] | None' = None,
    **kwargs,
)
  • path:这是第一个位置参数,也是读取 Parquet 文件时所需的唯一必填字段(至少需要此项)。在我们的示例中,你传递了名为 file 的 Python 路径对象作为参数。你也可以传递一个有效的 URL,指向远程 Parquet 文件的位置,例如 AWS S3 存储桶。

  • engine:如果你没有向引擎参数传递任何参数,则默认值为“auto”。另两个有效选项是“pyarrow”和“fastparquet”,具体取决于你安装的引擎。在我们的示例中,你安装了 PyArrow 库(参见 准备工作 部分)。“auto”选项将首先尝试加载 PyArrow,如果不可用,则回退到 fastparquet

  • columns:在这里,您可以指定在读取时希望限制的列。即使只选择一列,您也将其作为 Python 列表传递。在我们的示例中,您将列变量定义为columns = ['DATE', 'year', 'TMAX'],然后将其作为参数传递。

  • **kwargs:表示可以将额外的参数传递给引擎。在我们的案例中,我们使用了PyArrow库;因此,read_parquet() pandas 函数将这些参数传递给 PyArrow 库中的pyarrow.parquet.read_table()函数。在之前的示例中,我们将包含过滤条件的列表传递给了pyarrow.parquet.read_table()中的filters参数。有关可以使用的其他参数,您可以在这里查看pyarrow.parquet.read_table()的官方文档:arrow.apache.org/docs/python/generated/pyarrow.parquet.read_table.html

还有更多…

  1. 回想一下在准备工作部分中,您已将 PyArrow 库安装为处理 Parquet 文件的后端引擎。当您在 pandas 中使用read_parquet()读取函数时,pyarrow引擎是默认的。

  2. 既然您已经安装了该库,您可以直接利用它像使用 pandas 一样处理 Parquet 文件。您将使用pyarrow.parquet.read_table()函数,而不是pandas.read_parquet()函数,如下所示:

import pyarrow.parquet as pq
from pathlib import Path
file = Path('../../datasets/Ch2/LA_weather.parquet/')
table = pq.read_table(file, filters=filters, columns=columns)

table对象是pyarrow.Table类的一个实例。您可以使用以下代码验证这一点:

import pyarrow as pa
isinstance(table, pa.Table)
>> True

table对象包含许多有用的方法,包括.to_pandas()方法,用于将该对象转换为 pandas DataFrame:

df = table.to_pandas()

以下内容进一步说明了read_table()read_parquet()函数之间的相似性:

columns = ['DATE','year', 'TMAX']
filters = [('year', 'in', [2021, 2022, 2023])]
tb = pq.read_table(file,
                   filters=filters,
                   columns=columns,
                   use_pandas_metadata=True)
df_pa = tb.to_pandas()
df_pd = pd.read_parquet(file,
                        filters=filters,
                        columns=columns,
                        use_pandas_metadata=True)

df_padf_pd是等价的。

PyArrow 库提供了低级接口,而 pandas 提供了建立在 PyArrow 之上的高级接口。如果您决定安装fastparquet库,情况也是如此。请注意,PyArrow 是Apache Arrow的 Python 实现,Apache Arrow 是一个用于内存数据(列式内存)的开源项目。虽然 Apache Parquet 指定了用于高效存储和检索的列式文件格式,Apache Arrow 则使我们能够高效地在内存中处理如此庞大的列式数据集。

另见

To learn more about pandas.read_parquet() function, you can read their latest documentation here https://pandas.pydata.org/docs/reference/api/pandas.read_parquet.html

处理大数据文件

使用 pandas 的一个优点是,它提供了内存分析的数据结构,这使得处理数据时具有性能优势。然而,当处理大数据集时,这一优势也可能变成约束,因为您可以加载的数据量受可用内存的限制。当数据集超过可用内存时,可能会导致性能下降,尤其是在 pandas 为某些操作创建数据的中间副本时。

在现实场景中,有一些通用的最佳实践可以缓解这些限制,包括:

  • 采样或加载少量行进行探索性数据分析(EDA):在将数据分析策略应用于整个数据集之前,采样或加载少量行是一种良好的实践。这可以帮助您更好地了解数据,获得直觉,并识别可以删除的不必要列,从而减小整体数据集的大小。

  • 减少列数:仅保留分析所需的列可以显著减少数据集的内存占用。

  • 分块处理:利用 pandas 中许多读取函数提供的 chunksize 参数,您可以将数据分成较小、易于管理的块进行处理。此技巧有助于通过逐块处理来应对大规模数据集。

  • 使用其他大数据集库:有一些专门为处理大数据集设计的替代库,它们提供类似于 pandas 的 API,例如 DaskPolarsModin

在本教程中,您将学习 pandas 中处理大数据集的技巧,如 分块处理。随后,您将探索三个新库:DaskPolarsModin。这些库是 pandas 的替代方案,特别适用于处理大规模数据集。

准备工作

在本教程中,您将安装 Dask 和 Polars 库。

使用 pip 安装,您可以使用以下命令:

>>> pip install “dask[complete]"modin[all]" polars

使用 Conda 安装,您可以使用:

>>> conda install -c conda-forge dask polars modin-all

在本教程中,您将使用来自 www.nyc.gov/site/tlc/about/tlc-trip-record-data.page 的纽约出租车数据集,并且我们将使用 2023 年的 Yellow Taxi 旅行记录(涵盖 1 月至 5 月)。在本书的 GitHub 仓库中,我提供了 run_once() 函数,您需要执行一次。它将结合五个月的数据集(五个 parquet 文件),并生成一个大型 CSV 数据集(约 1.72 GB)。

以下是脚本作为参考:

# Script to create one large data file
import pandas as pd
import glob
def run_once():
    # Directory path where Parquet files are located
    directory = '../../datasets/Ch2/yellow_tripdata_2023-*.parquet'

    # Get a list of all Parquet files in the directory
    parquet_files = glob.glob(directory)

    # Read all Parquet files into a single DataFrame
    dfs = []
    for file in parquet_files:
        df = pd.read_parquet(file)
        dfs.append(df)

    # Concatenate all DataFrames into a single DataFrame
    combined_df = pd.concat(dfs)

    combined_df.to_csv('../../datasets/Ch2/yellow_tripdata_2023.csv', index=False)
run_once()

如何操作……

在本教程中,您将探索四种不同的方法来处理大型数据集以进行摄取。这些方法包括:

  • 使用 chunksize 参数,该参数在 pandas 的许多读取函数中都有提供。

  • 使用 Dask 库

  • 使用 Polars 库

memory_profiler 库将用于演示内存消耗。您可以使用 pip 安装该库:

Pip install -U memory_profiler

要在 Jupyter Notebook 中使用 memory_profiler,您需要先运行以下命令一次:

%load_ext memory_profiler

加载后,您可以在任何代码单元格中使用它。只需在 Jupyter 代码单元格中以 %memit%%memit 开头即可。典型的输出将显示峰值内存大小和增量大小。

  • 峰值内存表示在执行特定代码行时的最大内存使用量。

  • 增量表示当前行与上一行之间的内存使用差异。

使用 Chunksize

pandas 中有多个读取函数支持通过chunksize参数进行分块。当你需要导入一个大型数据集时,这种方法非常方便,但如果你需要对每个数据块执行复杂逻辑(这需要各个数据块之间的协调),它可能不适用。

pandas 中支持chunksize参数的一些读取函数包括:pandas.read_csv()pandas.read_table()pandas.read_sql()pandas.read_sql_query()pandas.read_sql_table()pandas.read_json()pandas.read_fwf()pandas.read_sas()pandas.read_spss(),以及pandas.read_stata()

  1. 首先,让我们开始使用传统方法,通过read_csv读取这个大型文件,而不进行数据块分割:
import pandas as pd
from pathlib import Path
file_path = Path('../../datasets/Ch2/yellow_tripdata_2023.csv')
%%time
%%memit
df_pd = pd.read_csv(file_path, low_memory=False)

由于我们有两个魔法命令%%time%%memit,输出将显示内存使用情况和 CPU 时间,如下所示:

peak memory: 10085.03 MiB, increment: 9922.66 MiB
CPU times: user 21.9 s, sys: 2.64 s, total: 24.5 s
Wall time: 25 s
  1. 使用相同的read_csv函数,你可以利用 chunksize 参数,它表示每个数据块读取的行数。在这个例子中,你将使用chunksize=10000,,这将每 10000 行创建一个数据块。
%%time
%%memit
df_pd = pd.read_csv(file_path, low_memory=False, chunksize=10000)

这将产生以下输出

peak memory: 3101.41 MiB, increment: 0.77 MiB
CPU times: user 25.5 ms, sys: 13.4 ms, total: 38.9 ms
Wall time: 516 ms

执行如此快速的原因是返回的是一个迭代器对象,类型为TextFileReader,如下所示:

type(df_pd)
pandas.io.parsers.readers.TextFileReader
  1. 要检索每个数据块中的数据,你可以使用get_chunk()方法一次检索一个数据块,或者使用循环检索所有数据块,或者简单地使用pandas.concat()函数:

  2. 选项 1:使用get_chunk()方法或 Python 的next()函数。这将一次检索一个数据块,每个数据块包含 10000 条记录。每次运行一个 get_chunk()或 next()时,你将获得下一个数据块。

%%time
%%memit
df_pd = pd.read_csv(file_path, low_memory=False, chunksize=10000)
df_pd.get_chunk()
>>
peak memory: 6823.64 MiB, increment: 9.14 MiB
CPU times: user 72.3 ms, sys: 40.8 ms, total: 113 ms
Wall time: 581 ms
# this is equivalent to
df_pd = pd.read_csv(file_path, low_memory=False, chunksize=10000)
Next(df_pd)
  1. 选项 2:遍历数据块。这在你希望对每个数据块执行简单操作后再合并各个数据块时非常有用:
%%time
%%memit
df_pd = pd.read_csv(file_path, low_memory=False, chunksize=10000)
final_result = pd.DataFrame()
for chunk in df_pd:
    final_result = pd.concat([final_result, chunk])
  1. 选项 3:使用 pd.concat()一次性检索所有数据块。这在整体性能上可能不如其他方法:
%%time
%%memit
df_pd = pd.read_csv(file_path, low_memory=False, chunksize=10000)
final_result = pd.concat(pf_pd)
>>
peak memory: 9145.42 MiB, increment: 697.86 MiB
CPU times: user 14.9 s, sys: 2.81 s, total: 17.7 s
Wall time: 18.8 s

内存和 CPU 时间的测量是为了说明问题。如你所见,遍历数据块并逐个追加每个数据块可能是一个耗时的过程。

接下来,你将学习如何使用 Polars,它提供了与 pandas 非常相似的 API,使得学习 Polars 的过渡变得更简单。

使用 Polars

类似于 pandas,Polars 库的设计也主要用于单台机器,但在处理大型数据集时,Polars 的性能优于 pandas。与单线程且无法利用单台机器多个核心的 pandas 不同,Polars 可以充分利用单台机器上所有可用的核心进行高效的并行处理。在内存使用方面,pandas 提供了内存数据结构,因此它在内存分析中非常流行。这也意味着,当你加载 CSV 文件时,整个数据集会被加载到内存中,因此,处理超出内存容量的数据集可能会遇到问题。另一方面,Polars 在执行类似操作时所需的内存比 pandas 少。

Polars 是用 Rust 编写的,这是一种性能与 C 和 C++ 相似的编程语言,由于其与 Python 相比的优越性能,Rust 在机器学习操作(MLOps)领域变得非常流行。

在本教程中,你将探索 Polars 的基础知识,主要是使用 read_csv() 读取大型 CSV 文件。

  1. 首先,导入 Polars 库:
import polars as pl
from pathlib import Path
file_path = Path('../../datasets/Ch2/yellow_tripdata_2023.csv')
  1. 现在你可以使用 read_csv 函数来读取 CSV 文件,类似于你使用 pandas 时的做法。注意为了演示目的,使用了 Jupyter 魔法命令 %%time%%memit
%%time
%%memit
df_pl = pl.read_csv(file_path)
>>
peak memory: 8633.58 MiB, increment: 2505.14 MiB
CPU times: user 4.85 s, sys: 3.28 s, total: 8.13 s
Wall time: 2.81 s
  1. 你可以使用 .head() 方法打印出 Polars DataFrame 的前 5 条记录,这与 pandas 类似。
df_pl.head()
  1. 要获取 Polars DataFrame 的总行数和列数,你可以使用 .shape 属性,类似于 pandas 的用法:
df_pl.shape
>>
(16186386, 20)
  1. 如果你决定使用 Polars 来处理大型数据集,但后来决定将结果输出为 pandas DataFrame,可以使用 .to_pandas() 方法来实现:
df_pd = df_pl.to_pandas()

从这些简单的代码运行中可以清楚地看出 Polars 的速度有多快。当将 Polars 库中的read_csv与 pandas 中的read_csv进行比较时,从内存和 CPU 指标上可以更加明显地看出这一点。

使用 Dask

另一个处理大型数据集的流行库是 Dask。它与 pandas 的 API 相似,但在分布式计算能力上有所不同,允许它超越单台机器进行扩展。Dask 与其他流行的库如 pandas、Scikit-Learn、NumPy 和 XGBoost 等集成得很好。

此外,你还可以安装 Dask-ML,这是一款附加库,提供可扩展的机器学习功能,并与流行的 Python ML 库如 Scikit-Learn、XGBoot、PyTorch 和 TensorFlow/Keras 等兼容。

在本教程中,你将探索 Polars 的基础知识,主要是使用 read_csv() 读取大型 CSV 文件。

  1. 首先,从 dask 库中导入 dataframe 模块:
import dask.dataframe as dd
from pathlib import Path
file_path = Path('../../datasets/Ch2/yellow_tripdata_2023.csv')
  1. 现在你可以使用 read_csv 函数来读取 CSV 文件,类似于你使用 pandas 时的做法。注意为了演示目的,使用了 Jupyter 魔法命令 %%time%%memit
%%time
%%memit
df_dk = dd.read_csv(file_path)
>>
peak memory: 153.22 MiB, increment: 3.38 MiB
CPU times: user 44.9 ms, sys: 12.1 ms, total: 57 ms
Wall time: 389 ms

以内存和 CPU 利用率方面的有趣输出为例。一个人可能会以为什么都没有被读取。让我们运行一些测试来了解发生了什么。

  1. 你将使用在 pandas 中常用的技术来探索 df_dk DataFrame,例如检查 DataFrame 的大小:
df_dk.shape
>>
(Delayed('int-0ab72188-de09-4d02-a76e-4a2c400e918b'), 20)
df_dk.info()
<class 'dask.dataframe.core.DataFrame'>
Columns: 20 entries, VendorID to airport_fee
dtypes: float64(13), int64(4), string(3)

注意,我们可以看到列的数量和它们的数据类型,但没有有关总记录数(行数)的信息。此外,注意 Dask 中的 Delayed 对象。我们稍后会再次提到它。

  1. 最后,尝试使用 print 函数输出 DataFrame:
print(df_dk)
>> 
Dask DataFrame Structure:
               VendorID tpep_pickup_datetime tpep_dropoff_datetime passenger_count trip_distance RatecodeID store_and_fwd_flag PULocationID DOLocationID payment_type fare_amount    extra  mta_tax tip_amount tolls_amount improvement_surcharge total_amount congestion_surcharge Airport_fee airport_fee
npartitions=26                                                                                                                                                                                                                                                                                            
                  int64               string                string         float64       float64    float64             string        int64        int64        int64     float64  float64  float64    float64      float64               float64      float64              float64     float64     float64
                    ...                  ...                   ...             ...           ...        ...                ...          ...          ...          ...         ...      ...      ...        ...          ...                   ...          ...                  ...         ...         ...
...                 ...                  ...                   ...             ...           ...        ...                ...          ...          ...          ...         ...      ...      ...        ...          ...                   ...          ...                  ...         ...         ...
                    ...                  ...                   ...             ...           ...        ...                ...          ...          ...          ...         ...      ...      ...        ...          ...                   ...          ...                  ...         ...         ...
                    ...                  ...                   ...             ...           ...        ...                ...          ...          ...          ...         ...      ...      ...        ...          ...                   ...          ...                  ...         ...         ...
Dask Name: to_pyarrow_string, 2 graph layers

输出非常有趣;显示的几乎完全是 DataFrame 的结构或布局,但没有数据。为了简化解释,Dask 采用了一种称为懒加载(lazy loading)或懒评估(lazy evaluation)策略。换句话说,Dask 中的大多数工作负载是懒惰的;它们不会立即执行,直到你通过特定操作触发它们,例如使用compute()方法。这个特性使得 Dask 能够通过延迟实际计算,直到它变得明确,从而处理大数据集和分布式计算。相反,Dask 几乎瞬间构建了一个任务图或执行逻辑,但任务图或执行逻辑并未被触发。

使用read_csv时,Dask 并不会立即加载整个数据集“”。它仅在你执行特定操作或函数时才读取数据。例如,使用head()方法将只检索前 5 条记录,仅此而已。这样可以节省内存并提高性能。

  1. 使用 head 方法打印 Dask DataFrame 的前五条记录:
df_dk.head()

你会注意到,前五(5)条记录的打印结果与使用 pandas 时的预期类似。

  1. 要获取数据集中的总记录数,你可以使用compute()方法,这将强制进行评估:
%%time
%%memit
print(df_dk.shape[0].compute())
>>
16186386
peak memory: 6346.53 MiB, increment: 1818.44 MiB
CPU times: user 19.3 s, sys: 5.29 s, total: 24.6 s
Wall time: 10.5 s
  1. 最后,如果你能够将 DataFrame 的大小缩小到便于 pandas 加载的程度,可以使用compute()方法将 Dask DataFrame 转换为 pandas DataFrame:
df_pd = df_dk.compute()
type(df_pd)
>>
pandas.core.frame.DataFrame

Dask 库提供了不同的 API。你只探讨了类似于 pandas 库的 DataFrame API。Dask 提供了许多优化功能,对于处理非常大数据集并需要扩展当前工作流程的人来说,它有一定的学习曲线,无论是从 NumPy、Scikit-Learn 还是 pandas。

它是如何工作的…

由于 pandas 的流行,许多库如 Polars 和 Dask 受到 pandas 简洁性和 API 的启发。因为这些库的设计目标是针对 pandas 用户,提供解决方案,解决 pandas 最重要的限制之一:缺乏扩展能力,无法处理无法放入内存的非常大数据集。

还有更多…

到目前为止,你已经了解了在处理大文件时,比使用 pandas 更好的选择,特别是当你有内存限制,无法将所有数据加载到可用内存中时。pandas 是一个单核框架,无法提供并行计算功能。相反,有一些专门的库和框架可以进行并行处理,旨在处理大数据。这些框架不依赖于将所有数据加载到内存中,而是可以利用多个 CPU 核心、磁盘使用,或扩展到多个工作节点(也就是多台机器)。之前,你探讨了Dask,它将数据分块,创建计算图,并在后台将较小的任务(分块)并行化,从而加速整体处理时间并减少内存开销。

这些框架虽然很好,但需要你花时间学习框架,可能还需要重写原始代码以利用这些功能。所以,最开始可能会有学习曲线。幸运的是,Modin项目正是在这一过程中发挥作用。Modin 库作为DaskRay的封装器,或者更具体地说,是其上面的抽象,使用与 pandas 类似的 API。Modin 让优化 pandas 代码变得更加简单,无需学习另一个框架;只需要一行代码。

首先导入必要的库:

from pathlib import Path
from modin.config import Engine
Engine.put("dask")  # Modin will use Dask
import modin.pandas as pd
file_path = Path('../../datasets/Ch2/yellow_tripdata_2023.csv')

注意几点,首先我们指定了要使用的引擎。在这种情况下,我们选择使用 Dask。Modin 支持其他引擎,包括 Ray 和 MPI。其次,请注意import modin.pandas as pd语句,这一行代码就足够将你的现有 pandas 代码进行扩展。请记住,Modin 项目仍在积极开发中,这意味着随着 pandas 的成熟和新功能的增加,Modin 可能仍在追赶。

让我们读取 CSV 文件并比较 CPU 和内存利用率:

%%time
%%memit
pd.read_csv(file_path)
>>
peak memory: 348.02 MiB, increment: 168.59 MiB
CPU times: user 1.23 s, sys: 335 ms, total: 1.57 s
Wall time: 8.26 s

你的数据加载很快,你可以运行其他 pandas 函数进一步检查数据,例如df_pd.head(), df_pd.info()df_pd.head(),你会注意到结果出现的速度非常快:

df_pd.info()
>>
<class 'modin.pandas.dataframe.DataFrame'>
RangeIndex: 16186386 entries, 0 to 16186385
Data columns (total 20 columns):
 #   Column                 Dtype 
---  ------                 ----- 
 0   VendorID               int64 
 1   tpep_pickup_datetime   object
 2   tpep_dropoff_datetime  object
 3   passenger_count        float64
 4   trip_distance          float64
 5   RatecodeID             float64
 6   store_and_fwd_flag     object
 7   PULocationID           int64 
 8   DOLocationID           int64 
 9   payment_type           int64 
 10  fare_amount            float64
 11  extra                  float64
 12  mta_tax                float64
 13  tip_amount             float64
 14  tolls_amount           float64
 15  improvement_surcharge  float64
 16  total_amount           float64
 17  congestion_surcharge   float64
 18  Airport_fee            float64
 19  airport_fee            float64
dtypes: float64(13), int64(4), object(3)
memory usage: 2.4+ GB

使用 Modin 可以让你在不学习新框架的情况下,利用现有的 pandas 代码、技能和库经验。这包括访问 pandas 的 I/O 函数(读写函数)以及你从 pandas 库中期望的所有参数。

另见

其他 Python 项目致力于使大数据集的处理更具可扩展性和性能,在某些情况下,它们提供了比 pandas 更好的选择。

第三章:3 从数据库中读取时间序列数据

加入我们的书籍社区,加入 Discord

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file0.png

packt.link/zmkOY

数据库扩展了你可以存储的内容,包括文本、图像和媒体文件,并且被设计用于在大规模下进行高效的读写操作。数据库能够存储数 TB 甚至 PB 的数据,并提供高效优化的数据检索功能,例如在执行分析操作时用于数据仓库数据湖。数据仓库是一个专门设计用于存储大量结构化数据的数据库,数据大多来自多个源系统的集成,专为支持商业智能报告、仪表板和高级分析而构建。另一方面,数据湖以原始格式存储大量结构化、半结构化或非结构化数据。本章中,我们将继续使用pandas库从数据库中读取数据。我们将通过读取关系型(SQL)数据库和非关系型(NoSQL)数据库的数据来创建时间序列 DataFrame。

此外,你将探索如何与第三方数据提供商合作,从他们的数据库系统中拉取金融数据。

在本章中,你将创建带有 DatetimeIndex 数据类型的时间序列 DataFrame,并涉及以下食谱:

  • 从关系型数据库中读取数据

  • 从 Snowflake 中读取数据

  • 从文档数据库中读取数据

  • 从时间序列数据库中读取数据

技术要求

在本章中,我们将广泛使用 pandas 2.2.2(于 2024 年 4 月 10 日发布)。

你将与不同类型的数据库一起工作,例如 PostgreSQL、Amazon Redshift、MongoDB、InfluxDB 和 Snowflake。你需要安装额外的 Python 库以连接到这些数据库。

你还可以从本书的 GitHub 仓库下载 Jupyter 笔记本(github.com/PacktPublishing/Time-Series-Analysis-with-Python-Cookbook)来进行跟随练习。

作为一个良好的实践,你会将你的数据库凭据存储在 Python 脚本之外的 database.cfg 配置文件中。你可以使用 configparser 来读取并将值存储在 Python 变量中。你不希望凭据暴露或硬编码在代码中:

# Example of configuration file "database.cfg file"
[SNOWFLAKE]
user=username
password=password
account=snowflakeaccount
warehouse=COMPUTE_WH
database=SNOWFLAKE_SAMPLE_DATA
schema=TPCH_SF1
role=somerole
[POSTGRESQL]
host: 127.0.0.1
dbname: postgres
user: postgres
password: password
[AWS]
host=<your_end_point.your_region.redshift.amazonaws.com>
port=5439
database=dev
username=<your_username>
password=<your_password>

你可以使用 config.read() 加载 database.cfg 文件:

import configparser
config = configparser.ConfigParser()
config.read(database.cfg')

从关系型数据库中读取数据

在这个食谱中,你将从 PostgreSQL 读取数据,这是一个流行的开源关系型数据库。

你将探索两种连接并与 PostgreSQL 交互的方法。首先,你将使用 psycopg,这是一个 PostgreSQL Python 连接器,来连接并查询数据库,然后将结果解析到 pandas DataFrame 中。第二种方法是再次查询相同的数据库,但这次你将使用SQLAlchemy,一个与 pandas 集成良好的对象关系映射器ORM)。

准备工作

在这个食谱中,假设你已经安装了最新版本的 PostgreSQL。在写这篇文章时,版本 16 是最新的稳定版本。

要在 Python 中连接和查询数据库,你需要安装 psycopg,它是一个流行的 PostgreSQL 数据库适配器。你还需要安装 SQLAlchemy,它提供了灵活性,可以根据你希望管理数据库的方式(无论是写入还是读取数据)来进行选择。

要使用 conda 安装库,运行以下命令:

>>> conda install -c conda-forge psycopg sqlalchemy -y

要使用 pip 安装库,运行以下命令:

>>> pip install sqlalchemy psycopg

如果你无法访问 PostgreSQL 数据库,最快的方式是通过 Docker (hub.docker.com/_/postgres) 来启动。以下是一个示例命令:

docker run -d \
    --name postgres-ch3 \
        -p 5432:5432 \
    -e POSTGRES_PASSWORD=password \
    -e PGDATA=/var/lib/postgresql/data/pgdata \
    postgres:16.4-alpine

这将创建一个名为 postgres-ch3 的容器。usernamepostgres,密码是 password。创建的默认 database 名为 postgres

一旦postgres-ch3容器启动并运行,你可以使用DBeaver进行连接,如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file22.png

图 3.1 – DBeaver PostgreSQL 连接设置

你将使用存放在 datasets/Ch3/MSFT.csv 文件夹中的 MSFT 股票数据集。我已经通过DBeaver Community Edition上传了数据集到数据库,你可以在这里下载 dbeaver.io/download/

你可以通过右键点击 public 模式下的 tables 来导入 CSV 文件,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file23.png

图 3.2 – 使用 DBeaver 导入数据到 PostgreSQL

你可以确认所有记录已被写入 postgres 数据库中的 msft 表,如下所示。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file24.png

图 3.3 – 在 DBeaver 中使用 SQL 编辑器运行 SQL 查询,以查询 msft 表

如何操作…

我们将首先连接到 PostgreSQL 实例,查询数据库,将结果集加载到内存中,并将数据解析为时间序列 DataFrame。

在这个食谱中,我将连接到一个本地运行的 PostgreSQL 实例,因此我的连接将是 localhost (127.0.0.1)。你需要根据你自己的 PostgreSQL 数据库设置来调整此连接。

使用 psycopg

Psycopg 是一个 Python 库(也是数据库驱动程序),它在使用 PostgreSQL 数据库时提供了额外的功能和特性。请按照以下步骤操作:

  1. 开始时导入必要的库。你将从 database.cfg 文件中导入所需的连接参数,正如技术要求部分中所突出显示的那样。你将创建一个 Python 字典来存储所有连接到数据库所需的参数值,比如 hostdatabase 名称、user 名称和 password
import psycopg
import pandas as pd
import configparser
config = configparser.ConfigParser()
config.read('database.cfg')
params = dict(config['POSTGRESQL'])
  1. 你可以通过将参数传递给 connect() 方法来建立连接。一旦连接成功,你可以创建一个游标对象,用来执行 SQL 查询:
conn = psycopg.connect(**params)
cursor = conn.cursor()
  1. 游标对象提供了多个属性和方法,包括 executeexecutemanyfetchallfetchmanyfetchone。以下代码使用游标对象传递 SQL 查询,然后使用 rowcount 属性检查该查询所产生的记录数:
cursor.execute("""
SELECT date, close, volume
FROM msft
ORDER BY date;
""")
cursor.rowcount
>> 1259
  1. 执行查询后返回的结果集将不包括标题(没有列名)。或者,你可以通过使用 description 属性从游标对象中获取列名,代码如下所示:
cursor.description
>>
[<Column 'date', type: varchar(50) (oid: 1043)>,
 <Column 'close', type: float4 (oid: 700)>,
 <Column 'volume', type: int4 (oid: 23)>]
  1. 你可以使用列表推导式从 cursor.description 中提取列名,并在创建 DataFrame 时将其作为列标题传入:
columns = [col[0] for col in cursor.description]
columns
>>
['date', 'close', 'volume']
  1. 要获取执行查询所产生的结果,你将使用 fetchall 方法。你将根据获取的结果集创建一个 DataFrame。确保传入你刚才捕获的列名:
data = cursor.fetchall()
df = pd.DataFrame(data, columns=columns)
df.info()
>>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1259 entries, 0 to 1258
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   date    1259 non-null   object
 1   close   1259 non-null   float64
 2   volume  1259 non-null   int64 
dtypes: float64(1), int64(1), object(1)
memory usage: 29.6+ KB

注意,date 列作为 object 类型返回,而不是 datetime 类型。

  1. 使用 pd.to_datetime() 解析 date 列,并将其设置为 DataFrame 的索引:
df['date'] = pd.to_datetime(df['date'])
df = df.set_index('date')
print(df.tail(3))
>>
             close    volume
date                       
2024-08-30  417.14  24308300
2024-09-03  409.44  20285900
2024-09-04  408.84   9167942

在前面的代码中,游标返回了一个没有标题的元组列表**(没有列名)**。你可以通过运行以下代码来确认这一点:

data = cursor.fetchall()
data[0:5]
>>
[('2019-09-04', 131.45726, 17995900),
 ('2019-09-05', 133.7687, 26101800),
 ('2019-09-06', 132.86136, 20824500),
 ('2019-09-09', 131.35222, 25773900),
 ('2019-09-10', 129.97684, 28903400)]

你可以指示游标返回一个 dict_row 类型,它将包括列名信息(即标题)。这在转换为 DataFrame 时更为方便。可以通过将 dict_row 类传递给 row_factory 参数来实现:

from psycopg.rows import dict_row
conn = psycopg.connect(**params, row_factory=dict_row)
cursor = conn.cursor()
cursor.execute("SELECT * FROM msft;")
data = cursor.fetchall()
data[0:2]
>>
[{'date': '2019-09-04',
  'open': 131.14206,
  'high': 131.51457,
  'low': 130.35883,
  'close': 131.45726,
  'volume': 17995900},
 {'date': '2019-09-05',
  'open': 132.87086,
  'high': 134.08391,
  'low': 132.53656,
  'close': 133.7687,
  'volume': 26101800}]

注意列名已经可用。你现在可以像以下代码所示那样创建一个 DataFrame:

df = pd.DataFrame(data)
print(df.head())
>>
        date       open       high        low      close    volume
0  2019-09-04  131.14206  131.51457  130.35883  131.45726  17995900
1  2019-09-05  132.87086  134.08391  132.53656  133.76870  26101800
2  2019-09-06  133.74963  133.89291  132.00171  132.86136  20824500
3  2019-09-09  133.32938  133.48220  130.33977  131.35222  25773900
4  2019-09-10  130.66455  130.75050  128.47725  129.97684  28903400
  1. 关闭游标和数据库连接:
cursor.close()
conn.close()

注意,psycopg 连接和游标可以在 Python 的 with 语句中用于处理异常,以便在提交事务时进行异常处理。游标对象提供了三种不同的获取函数;即 fetchallfetchmanyfetchonefetchone 方法返回一个单独的元组。以下示例展示了这一概念:

with psycopg.connect(**params) as conn:
     with conn.cursor() as cursor:
            cursor.execute('SELECT * FROM msft')
            data = cursor.fetchone()
print(data)
>>
('2019-09-04', 131.14206, 131.51457, 130.35883, 131.45726, 17995900)
使用 pandas 和 SQLAlchemy

SQLAlchemy 是一个非常流行的开源库,用于在 Python 中操作关系型数据库。SQLAlchemy 可以被称为对象关系映射器(ORM),它提供了一个抽象层(可以把它当作一个接口),让你可以使用面向对象编程与关系型数据库进行交互。

你将使用 SQLAlchemy,因为它与 pandas 的集成非常好,多个 pandas SQL 读写函数依赖于 SQLAlchemy 作为抽象层。SQLAlchemy 会为任何 pandas 的 SQL 读写请求做幕后翻译。这种翻译确保了 pandas 中的 SQL 语句会以适用于底层数据库类型(如 MySQL、Oracle、SQL Server 或 PostgreSQL 等)的正确语法/格式表示。

一些依赖于 SQLAlchemy 的 pandas SQL 读取函数包括 pandas.read_sqlpandas.read_sql_querypandas.read_sql_table。让我们执行以下步骤:

  1. 开始时导入必要的库。注意,在幕后,SQLAlchemy 将使用 psycopg(或任何其他已安装且 SQLAlchemy 支持的数据库驱动程序):
import pandas as pd
from sqlalchemy import create_engine
engine =\
    create_engine("postgresql+psycopg://postgres:password@localhost:5432/postgres")
query = "SELECT * FROM msft"
df = pd.read_sql(query,
                 engine,
                 index_col='date',
                 parse_dates={'date': '%Y-%m-%d'})
print(df.tail(3))
>>
              open    high     low   close    volume
date                                               
2024-08-30  415.60  417.49  412.13  417.14  24308300
2024-09-03  417.91  419.88  407.03  409.44  20285900
2024-09-04  405.63  411.24  404.37  408.84   9167942

在前面的示例中,对于 parse_dates,你传递了一个字典格式的参数 {key: value},其中 key 是列名,value 是日期格式的字符串表示。与之前的 psycopg 方法不同,pandas.read_sql 更好地处理了数据类型的正确性。注意,我们的索引是 DatetimeIndex 类型:

df.info()
>>
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1259 entries, 2019-09-04 to 2024-09-04
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   open    1259 non-null   float64
 1   high    1259 non-null   float64
 2   low     1259 non-null   float64
 3   close   1259 non-null   float64
 4   volume  1259 non-null   int64 
dtypes: float64(4), int64(1)
memory usage: 59.0 KB
  1. 你也可以使用 pandas.read_sql_query 来完成相同的操作:
df = pd.read_sql_query(query,
                       engine,
                       index_col='date',
                       parse_dates={'date':'%Y-%m-%d'})
  1. pandas.read_sql_table 是 pandas 提供的另一个 SQL 读取函数,它接受表名而不是 SQL 查询。可以把它看作是一个 SELECT * FROM tablename 查询:
df = pd.read_sql_table('msft',
                        engine,
                        index_col='date',
                        parse_dates={'date':'%Y-%m-%d'})

read_sql 读取函数更为通用,因为它是 read_sql_queryread_sql_table 的封装器。read_sql 函数既可以接受 SQL 查询,也可以接受表名。

它是如何工作的……

在本教程中,你探索了两种连接 PostgreSQL 数据库的方法:直接使用 psycopg 驱动程序或利用 pandas 和 SQLAlchemy。

使用psycopg连接 PostgreSQL 时,首先需要创建一个连接对象,然后创建一个游标对象。连接对象和游标的概念在 Python 中的不同数据库驱动程序之间是一致的。创建游标对象后,可以访问多个方法,包括:

  • Execute() – 执行 SQL 查询(CRUD)或命令到数据库

  • Executemany() – 使用一系列输入数据执行相同的数据库操作,例如,这对于批量插入的 INSERT INTO 操作非常有用。

  • Fetchall() – 返回当前查询结果集中的所有剩余记录

  • Fetchone() - 从当前查询结果集中返回下一条记录(单条记录)

  • fetchmany(n) – 从当前查询结果集中返回 n 条记录

  • close() - 关闭当前游标并释放关联的资源

另一方面,创建 engine 对象是使用 SQLAlchemy 时的第一步,因为它提供了关于正在使用的数据库的说明,这被称为 dialect(方言)。

当你使用 create_engine 创建引擎时,你传递了一个作为连接字符串的 URL。让我们检查一下 SQLAlchemy 的引擎连接字符串:

create_engine("dialect+driver://username:password@host:port/database")
  • dialect – SQLAlchemy 方言(数据库类型)的名称,例如 postgresql、mysql、sqlite、oracle 或 mssql。

  • driver – 连接指定方言的已安装驱动程序(DBAPI)的名称,例如,PostgreSQL 数据库的 psycopgpg8000

  • username - 数据库身份验证的登录用户名

  • password - 为指定的用户名设置的密码

  • host - 数据库所在的服务器

  • port - 数据库的特定端口

  • database - 你要连接的具体数据库的名称

之前,你使用 psycopg 作为 PostgreSQL 的数据库驱动程序。psycopg 驱动程序被称为 数据库 API (DBAPI),SQLAlchemy 支持多种基于 Python DBAPI 规范的 DBAPI 包装器,用于连接和与各种类型的关系型数据库进行交互。SQLAlchemy 已经内置了多种方言,以便与不同类型的关系数据库管理系统(RDBMS)协作,具体包括:

  • SQL Server

  • SQLite

  • PostgreSQL

  • MySQL

  • Oracle

  • Snowflake

当使用 SQLAlchemy 连接到数据库时,你需要指定要使用的 方言驱动程序(DBAPI)。这是 PostgreSQL 的 URL 字符串格式:

create_engine("postgresql+psycopg2://username:password@localhost:5432/dbname")

如果你使用 MySQL 数据库并配合 PyMySQL 驱动,连接 URL 将如下所示:

create_engine("mysql+pymysql://username:password@localhost:3306/dbname")

在前面 如何做…… 部分的代码示例中,你无需指定 psycopg 驱动程序,因为它是 SQLAlchemy 默认使用的 DBAPI。假设已经安装了 psycopg,此示例将可以正常工作:

create_engine("postgresql://username:password@localhost:5432/dbname")

SQLAlchemy 支持其他 PostgreSQL 驱动程序(DBAPI),包括以下内容:

  • psycopg

  • pg8000

  • asyncpg

如需查看更全面的支持的方言和驱动程序列表,你可以访问官方文档页面 docs.sqlalchemy.org/en/20/dialects/index.html

使用 SQLAlchemy 的优势在于,它与 pandas 集成良好。如果你查阅官方 pandas 文档中的 read_sqlread_sql_queryread_sql_tableto_sql,你会注意到 con 参数期望一个 SQLAlchemy 连接对象(引擎)。

另一个优势是,你可以轻松更换后端引擎(数据库),例如从 PostgreSQL 切换到 MySQL,而无需修改其余代码。

还有更多……

在本节中,我们将探讨一些附加概念,帮助你更好地理解 SQLAlchemy 的多功能性,并将第二章 处理大型数据文件 中介绍的一些概念与本教程结合起来,这些概念讨论了如何从文件中读取时间序列数据。

具体来说,我们将讨论以下内容:

  • 在 SQLAlchemy 中生成连接 URL

  • 扩展到 Amazon Redshift 数据库

  • pandas 中的分块处理

在 SQLAlchemy 中生成连接 URL

在这个教程中,你已经学习了 SQLAlchemy 库中的 create_engine 函数,它通过 URL 字符串来建立数据库连接。到目前为止,你一直是手动创建 URL 字符串,但有一种更方便的方式可以为你生成 URL。这可以通过 SQLAlchemy 中 URL 类的 create 方法来实现。

以下代码演示了这一点:

from sqlalchemy import URL, create_engine
url = URL.create(
    drivername='postgresql+psycopg',
    host= '127.0.0.1',
    username='postgres',
    password='password',
    database='postgres',
    port= 5432
)
>>
postgresql+psycopg://postgres:***@127.0.0.1:5432/postgres

请注意,drivername 包含 方言驱动程序,格式为 dialct+driver

现在,你可以像以前一样将 url 传递给 create_engine

engine = create_engine(url)
df = pd.read_sql('select * from msft;', engine)
扩展到 Amazon Redshift 数据库

我们讨论了 SQLAlchemy 的多功能性,它允许你更改数据库引擎(后端数据库),而保持其余代码不变。例如,使用 PostgreSQL 或 MySQL,或者 SQLAlchemy 支持的任何其他方言。我们还将探讨如何连接到像 Amazon Redshift 这样的云数据仓库。

值得一提的是,Amazon Redshift是一个基于 PostgreSQL 的云数据仓库。你将安装适用于 SQLAlchemy 的 Amazon Redshift 驱动程序(它使用 psycopg2 DBAPI)。

你也可以使用conda进行安装:

conda install conda-forge::sqlalchemy-redshift

你也可以使用pip进行安装:

pip install sqlalchemy-redshift

因为我们不希望在代码中暴露你的 AWS 凭证,你将更新在技术要求部分讨论的database.cfg文件,以包含你的 AWS Redshift 信息:

[AWS]
host=<your_end_point.your_region.redshift.amazonaws.com>
port=5439
database=dev
username=<your_username>
password=<your_password>

你将使用configparser加载你的值:

from configparser import ConfigParser
config = ConfigParser()
config.read('database.cfg')
config.sections()
params = dict(config['AWS'])

你将使用 URL.create 方法来生成你的 URL:

url = URL.create('redshift+psycopg2', **params)
aws_engine = create_engine(url)

现在,你可以将之前代码中的引擎切换,从指向本地 PostgreSQL 实例的引擎,改为在 Amazon Redshift 上运行相同的查询。这假设你在 Amazon Redshift 中有一个msft表。

df = pd.read_sql(query,
                 aws_engine,
                 index_col='date',
                 parse_dates=True)

要了解更多关于sqlalchemy-redshift的信息,可以访问该项目的仓库:github.com/sqlalchemy-redshift/sqlalchemy-redshift

Amazon Redshift 的例子可以扩展到其他数据库,例如 Google BigQuery、Teradata 或 Microsoft SQL Server,只要这些数据库有支持的 SQLAlchemy 方言即可。要查看完整的列表,请访问官方页面:docs.sqlalchemy.org/en/20/dialects/index.html

使用 pandas 进行 chunking

当你执行针对msft表的查询时,它返回了 1259 条记录。试想一下,如果处理一个更大的数据库,可能会返回数百万条记录,甚至更多。这就是chunking参数派上用场的地方。

chunksize参数允许你将一个大的数据集拆分成较小的、更易于管理的数据块,这些数据块能够适应本地内存。当执行read_sql函数时,只需将要检索的行数(每个数据块)传递给chunksize参数,之后它会返回一个generator对象。你可以循环遍历这个生成器对象,或者使用next()一次获取一个数据块,并进行所需的计算或处理。让我们看一个如何实现 chunking 的例子。你将每次请求500条记录(行):

df_gen = pd.read_sql(query,
                 engine,
                 index_col='date',
                 parse_dates=True,
                 chunksize=500)

上面的代码将生成三个(3)数据块。你可以像下面这样遍历df_gen生成器对象:

for idx, data in enumerate(df_gen):
     print(idx, data.shape)
>>
0 (500, 5)
1 (500, 5)
2 (259, 5)

上面的代码展示了 chunking 如何工作。使用chunksize参数应该减少内存使用,因为每次加载的行数较少。

另见:

若要获取关于这些主题的更多信息,请查看以下链接:

从 Snowflake 读取数据

一个非常常见的数据分析提取来源通常是公司的数据仓库。数据仓库托管了大量的数据,这些数据大多是集成的,用来支持各种报告和分析需求,此外还包含来自不同源系统的历史数据。

云计算的发展为我们带来了云数据仓库,如Amazon RedshiftGoogle BigQueryAzure SQL Data WarehouseSnowflake

在这个教程中,你将使用Snowflake,一个强大的软件即服务SaaS)基于云的数据仓库平台,可以托管在不同的云平台上,例如Amazon Web ServicesAWS)、Google Cloud PlatformGCP)和Microsoft Azure。你将学习如何使用 Python 连接到 Snowflake,提取时间序列数据,并将其加载到 pandas DataFrame 中。

准备工作

这个教程假设你有访问 Snowflake 的权限。你将探索三种(3)不同的方法来连接 Snowflake,因此你需要安装三种(3)不同的库。

推荐的雪花连接器-python库安装方法是使用pip,这样可以让你安装像pandas这样的附加组件,如下所示:

pip install snowflake-sqlalchemy snowflake-snowpark-python
pip install "snowflake-connector-python[pandas]"

你也可以使用conda进行安装,但如果你想要将snowflake-connector-python与 pandas 一起使用,你需要使用 pip 安装。

conda install -c conda-forge snowflake-sqlalchemy snowflake-snowpark-python
conda install -c conda-froge snowflake-connector-python

确保你在技术要求部分创建的配置文件database.cfg包含了你的Snowflake连接信息:

[SNOWFLAKE]
user=username
password=password
account=snowflakeaccount
warehouse=COMPUTE_WH
database=SNOWFLAKE_SAMPLE_DATA
schema=TPCH_SF1
role=somerole

在这个教程中,你将使用SNOWFLAKE_SAMPLE_DATA数据库和 Snowflake 提供的TPCH_SF1模式。

获取正确的account值可能会让许多人感到困惑。为了确保你获得正确的格式,请使用 Snowflake 中的复制帐户 URL选项,它可能像这样https://abc1234.us-east-1.snowflakecomputing.com,其中abc1234.us-east-1部分是你将用作account值的部分。

如何操作…

我们将探索三种(3)方法和库来连接到 Snowflake 数据库。在第一种方法中,你将使用 Snowflake Python 连接器建立连接,并创建一个游标来查询和提取数据。在第二种方法中,你将使用 Snowflake SQLAlchemy。在第三种方法中,你将探索Snowpark Python API。让我们开始吧:

使用 snowflake-connector-python
  1. 我们将从导入必要的库开始:
import pandas as pd
from snowflake import connector
from configparser import ConfigParser
  1. 使用 ConfigParser,你将提取 [SNOWFLAKE] 部分下的内容,以避免暴露或硬编码你的凭据。你可以读取 [SNOWFLAKE] 部分的所有内容并将其转换为字典对象,如下所示:
config = ConfigParser()
config.read(database.cfg')
params = dict(config['SNOWFLAKE'])
  1. 你需要将参数传递给 connector.connect() 来与 Snowflake 建立连接。我们可以轻松地 解包 字典内容,因为字典的键与参数名匹配。一旦连接建立,我们可以创建我们的 游标
con = connector.connect(**params)
cursor = con.cursor()
  1. 游标对象有许多方法,如 executefetchallfetchmanyfetchonefetch_pandas_allfetch_pandas_batches

你将从 execute 方法开始,向数据库传递 SQL 查询,然后使用任何可用的获取方法来检索数据。在以下示例中,你将查询 ORDERS 表,然后利用 fetch_pandas_all 方法将整个结果集作为 pandas DataFrame 检索:

query = "SELECT * FROM ORDERS;"
cursor.execute(query)
df = cursor.fetch_pandas_all()

之前的代码可以写成如下:

df = cursor.execute(query).fetch_pandas_all()
  1. 使用 df.info() 检查 DataFrame:
df.info()
>>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1500000 entries, 0 to 1499999
Data columns (total 9 columns):
 #   Column           Non-Null Count    Dtype 
---  ------           --------------    ----- 
 0   O_ORDERKEY       1500000 non-null  int32 
 1   O_CUSTKEY        1500000 non-null  int32 
 2   O_ORDERSTATUS    1500000 non-null  object
 3   O_TOTALPRICE     1500000 non-null  float64
 4   O_ORDERDATE      1500000 non-null  object
 5   O_ORDERPRIORITY  1500000 non-null  object
 6   O_CLERK          1500000 non-null  object
 7   O_SHIPPRIORITY   1500000 non-null  int8  
 8   O_COMMENT        1500000 non-null  object
dtypes: float64(1), int32(2), int8(1), object(5)
memory usage: 81.5+ MB
  1. 从前面的输出中可以看到,DataFrame 的索引仅是一个数字序列,且 O_ORDERDATE 列不是一个 Date 类型的字段。你可以使用 pandas.to_datetime() 函数将 O_ORDERDATE 列解析为日期时间类型,然后使用 DataFrame.set_index() 方法将该列设置为 DataFrame 的索引:
df_ts = (
    df.set_index(
                pd.to_datetime(df['O_ORDERDATE'])
                )
                .drop(columns='O_ORDERDATE'))

让我们显示 df_ts DataFrame 的前四(4)列和前五(5)行:

print(df_ts.iloc[0:3, 1:5])
>>
             O_CUSTKEY O_ORDERSTATUS  O_TOTALPRICE  O_ORDERPRIORITY
O_ORDERDATE                                                       
1994-02-21       13726             F      99406.41         3-MEDIUM
1997-04-14      129376             O     256838.41  4-NOT SPECIFIED
1997-11-24      141613             O     150849.49  4-NOT SPECIFIED
  1. 检查 DataFrame 的索引。打印前两个索引:
df_ts.index[0:2]
>>
DatetimeIndex(['1994-02-21', '1997-04-14'], dtype='datetime64[ns]', name='O_ORDERDATE', freq=None)
  1. 最后,你可以关闭游标和当前连接。
Cursor.close()
con.close()

现在你拥有一个具有DatetimeIndex的时间序列 DataFrame。

使用 SQLAlchemy

在之前的例子中,从关系数据库读取数据,你探索了 pandas 的 read_sqlread_sql_queryread_sql_table 函数。这是通过使用 SQLAlchemy 和安装一个支持的方言来完成的。在这里,我们将在安装 snowflake-sqlalchemy 驱动程序后使用 Snowflake 方言。

SQLAlchemy 与 pandas 更好地集成,正如你将在本节中体验的那样。

  1. 开始时,导入必要的库,并从 database.cfg 文件中的 [SNOWFLAKE] 部分读取 Snowflake 连接参数。
from sqlalchemy import create_engine
from snowflake.sqlalchemy import URL
import configparser
config = ConfigParser()
config.read('database.cfg')
params = dict(config['SNOWFLAKE'])
  1. 你将使用 URL 类来生成 URL 连接字符串。我们将创建我们的引擎对象,然后使用 engine.connect() 打开连接:
url = URL(**params)
engine = create_engine(url)
connection = engine.connect()
  1. 现在,你可以使用 read_sqlread_sql_query 来对 SNOWFLAKE_SAMPLE_DATA 数据库中的 ORDERS 表执行查询:
query = "SELECT * FROM ORDERS;"
df = pd.read_sql(query,
                 connection,
                 index_col='o_orderdate',
                 parse_dates='o_orderdate')
df.info()
>>
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1500000 entries, 1992-04-22 to 1994-03-19
Data columns (total 8 columns):
 #   Column           Non-Null Count    Dtype 
---  ------           --------------    ----- 
 0   o_orderkey       1500000 non-null  int64 
 1   o_custkey        1500000 non-null  int64 
 2   o_orderstatus    1500000 non-null  object
 3   o_totalprice     1500000 non-null  float64
 4   o_orderpriority  1500000 non-null  object
 5   o_clerk          1500000 non-null  object
 6   o_shippriority   1500000 non-null  int64 
 7   o_comment        1500000 non-null  object
dtypes: float64(1), int64(3), object(4)
memory usage: 103.0+ MB

注意与之前使用 Snowflake Python 连接器的方法相比,我们是如何在一个步骤中解析 o_orderdate 列并将其设置为索引的。

  1. 最后,关闭数据库连接。
connection.close()
engine.dispose()

通过使用 上下文管理器,可以进一步简化代码,以自动分配和释放资源。以下示例使用了 with engine.connect()

query = "SELECT * FROM ORDERS;"
url = URL(**params)
engine = create_engine(url)
with engine.connect() as connection:
    df = pd.read_sql(query,
                      connection,
                      index_col='o_orderdate',
                      parse_dates=['o_orderdate'])
df.info()

这样应该能够得到相同的结果,而无需关闭连接或处理引擎。

使用 snowflake-snowpark-python

Snowpark API 支持 Java、Python 和 Scala。你已经按照本配方中 准备工作 部分的描述安装了 snowflake-snowpark-python

  1. 从导入必要的库并从 database.cfg 文件的 [SNOWFLAKE] 部分读取 Snowflake 连接参数开始
from snowflake.snowpark import Session
from configparser import ConfigParser
config = ConfigParser()
config.read('database.cfg')
params = dict(config['SNOWFLAKE'])
  1. 通过与 Snowflake 数据库建立连接来创建会话
session = Session.builder.configs(params).create()
  1. 会话有多个 DataFrameReader 方法,如 readtablesql。这些方法中的任何一个都会返回一个 Snowpark DataFrame 对象。返回的 Snowpark DataFrame 对象可以使用 to_pandas 方法转换为更常见的 pandas DataFrame。你将探索 readtablesql 方法,以返回相同的结果集。

read 方法开始。更具体地说,你将使用 read.table 并传入一个表名。这将返回该表的内容并通过 to_pandas 方法转换为 pandas DataFrame。可以将其视为等同于 SELECT * FROM TABLE

orders = session.read.table("ORDERS").to_pandas()

类似地,table 方法接受一个表名,返回的对象(Snowpark DataFrame)可以使用 to_pandas 方法:

orders = session.table("ORDERS").to_pandas()

最后,sql 方法接受一个 SQL 查询:

query = 'SELECT * FROM ORDERS'
orders = session.sql(query).to_pandas()
orders.info()
>>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1500000 entries, 0 to 1499999
Data columns (total 9 columns):
 #   Column           Non-Null Count    Dtype 
---  ------           --------------    ----- 
 0   O_ORDERKEY       1500000 non-null  int32 
 1   O_CUSTKEY        1500000 non-null  int32 
 2   O_ORDERSTATUS    1500000 non-null  object
 3   O_TOTALPRICE     1500000 non-null  float64
 4   O_ORDERDATE      1500000 non-null  object
 5   O_ORDERPRIORITY  1500000 non-null  object
 6   O_CLERK          1500000 non-null  object
 7   O_SHIPPRIORITY   1500000 non-null  int8  
 8   O_COMMENT        1500000 non-null  object
dtypes: float64(1), int32(2), int8(1), object(5)
memory usage: 81.5+ MB

这三种方法应该产生相同的 pandas DataFrame。

它是如何工作的…

Snowflake Python 连接器、Snowflake SQLAlchemy 驱动程序和 Snowpark Python 需要相同的输入变量来建立与 Snowflake 数据库的连接。这些包括以下内容:

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file25.jpg

表 3.1 – Snowflake Python 连接器的输入变量

回想一下,在前一个活动中,你在 database.cfg 文件的 [SNOWFLAKE] 部分为所有三种方法使用了相同的配置。

Snowflake Python 连接器

当使用 Python 连接器时,你首先通过 con = connector.connect(**params) 来建立与数据库的连接。一旦连接被接受,你使用 cursor = con.cursor() 创建一个游标对象。

游标提供了执行和获取操作的方法,如 describe()execute()execute_async()executemany()fetchone()fetchall()fetchmany()fetch_pandas_all()fetch_pandas_batches(),每个游标还具有若干属性,包括 descriptionrowcountrownumber 等。注意,当使用 Python 连接器 psycopg 时,之前的配方 从关系数据库读取数据 中讨论了熟悉的方法和属性。

  • Execute() – 执行 SQL 查询(CRUD)或命令到数据库

  • executemany() – 使用一系列输入数据执行相同的数据库操作,例如,这在使用 INSERT INTO 进行批量插入时非常有用。

  • Fetchall() – 返回当前查询结果集中的所有剩余记录

  • fetchone() - 从当前查询结果集中返回下一条记录(一条记录)

  • fetchmany(n) – 从当前查询结果集中返回n条记录

  • fetch_pandas_all() - 返回当前查询结果集中的所有剩余记录,并将它们加载到 pandas DataFrame 中

  • fetch_pandas_batches() - 返回当前查询结果集中的一部分剩余记录,并将它们加载到 pandas DataFrame 中

  • close() - 关闭当前游标并释放相关资源

  • describe() – 返回结果集的元数据,但不会执行查询。或者,你可以使用execute(),然后使用description属性来获取相同的元数据信息。

要查看完整的属性和方法列表,请参考官方文档:docs.snowflake.com/en/user-guide/python-connector-api.html#object-cursor

SQLAlchemy API

当使用 SQLAlchemy 时,你可以利用pandas.read_sqlpandas.read_sql_querypandas.read_sql_query读取函数,并利用许多可用参数在读取时转换和处理数据,如index_colparse_dates。另一方面,当使用 Snowflake Python 连接器时,fetch_pandas_all()函数不接受任何参数,你需要在之后解析和调整 DataFrame。

Snowflake SQLAlchemy 库提供了一个方便的方法URL,帮助构建连接字符串以连接到 Snowflake 数据库。通常,SQLAlchemy 期望提供以下格式的 URL:

'snowflake://<user>:<password>@<account>/<database>/<schema>
?warehouse=<warehouse>&role=<role>'

使用URL方法,我们传递了参数,方法会自动构造所需的连接字符串:

engine = create_engine(URL(
    account = '<your_account>',
    user = '<your_username>',
    password = '<your_password>',
    database = '<your_database>',
    schema = '<your_schema>',
    warehouse = '<your_warehouse>',
    role='<your_role>',
))

或者,我们将 Snowflake 参数存储在配置文件 database.cfg 中,并以 Python 字典的形式存储。这样,你就不会在代码中暴露你的凭证。

params = dict(config['SNOWFLAKE'])
url = create_engine(URL(**params))

如果你比较本食谱中的过程,使用SQLAlchemy连接 Snowflake,与之前食谱中的从关系数据库读取数据过程,你会发现两者在过程和代码上有许多相似之处。这就是使用 SQLAlchemy 的优势之一,它为各种数据库创建了一个标准流程,只要 SQLAlchemy 支持这些数据库。SQLAlchemy 与 pandas 集成得很好,可以轻松地切换方言(后端数据库),而无需对代码做太多修改

Snowpark API

在之前的方法中,你只是使用了snowflake-connector-pythonsnowflake-connector-python库作为连接器连接到你的 Snowflake 数据库,然后提取数据以在本地处理。

Snowpark 不仅仅提供了一种连接数据库的机制。它允许您直接在 Snowflake 云环境中处理数据,而无需将数据移出或在本地处理。此外,Snowpark 还非常适合执行更复杂的任务,如构建复杂的数据管道或使用 Snowpark ML 处理机器学习模型,所有这些都在 Snowflake 云中完成。

在我们的方案中,类似于其他方法,我们需要与 Snowflake 建立连接。这是通过使用Session类来实现的。

params = dict(config['SNOWFLAKE'])
session = Session.builder.configs(params).create()

Snowpark 和 PySpark(Spark)在 API 和概念上有很多相似之处。更具体地说,Snowpark DataFrame 被视为延迟求值的关系数据集to_pandas方法执行了两件事:它执行查询并将结果加载到 pandas DataFrame 中(数据会被从 Snowflake 外部提取)。要将 pandas DataFrame 转换回 Snowpark DataFrame(在 Snowflake 内部),您可以使用如下的create_dataframe方法:

df = session.create_dataframe(orders)

为了成功执行前面的代码,您需要具有写权限,因为 Snowflake 会创建一个临时表来存储 pandas DataFrame(在 Snowflake 中),然后返回一个指向该临时表的 Snowpark DataFrame。或者,如果您希望将 pandas DataFrame 持久化到一个表中,您可以使用如下的write_pandas方法:

df = session.write_pandas(orders, table_name='temp_table')

在前面的代码中,您传递了 pandas DataFrame 和表名。

还有更多…

您可能已经注意到,当使用 Snowflake Python 连接器和 Snowpark 时,返回的 DataFrame 中的列名全部以大写字母显示,而在使用 Snowflake SQLAlchemy 时,它们则是小写的。

之所以如此,是因为 Snowflake 默认在创建对象时将未加引号的对象名称存储为大写。例如,在之前的代码中,我们的Order Date列被返回为O_ORDERDATE

为了明确指出名称是区分大小写的,您在创建 Snowflake 对象时需要使用引号(例如,'o_orderdate''OrderDate')。相对而言,使用 Snowflake SQLAlchemy 时,默认会将名称转换为小写。

另见

从文档数据库读取数据

MongoDB 是一个NoSQL 数据库,使用文档存储数据,并使用 BSON(一种类似 JSON 的结构)来存储无模式的数据。与关系型数据库不同,关系型数据库中的数据存储在由行和列组成的表中,而面向文档的数据库则将数据存储在集合和文档中。

文档代表存储数据的最低粒度,就像关系型数据库中的行一样。集合像关系型数据库中的表一样存储文档。与关系型数据库不同,集合可以存储不同模式和结构的文档。

准备工作

在本教程中,假设你已经有一个正在运行的 MongoDB 实例。为准备此教程,你需要安装 PyMongo Python 库来连接 MongoDB。

要使用 conda 安装 MongoDB,请运行以下命令:

conda install -c conda-forge pymongo -y

要使用 pip 安装 MongoDB,请运行以下命令:

python -m pip install pymongo

如果你无法访问 PostgreSQL 数据库,最快的启动方式是通过 Docker (hub.docker.com/_/mongo)。以下是一个示例命令:

docker run -d \
    --name mongo-ch3 \
    -p 27017:27017 \
    --env MARIADB_ROOT_PASSWORD=password \
    mongo:8.0-rc

另外,你可以尝试免费使用MongoDB Atlas,访问 www.mongodb.com/products/platform/atlas-databaseMongoDB Atlas 是一个完全托管的云数据库,可以部署在你喜欢的云提供商上,如 AWS、Azure 和 GCP。

关于使用 MongoDB Atlas 的注意事项

如果你连接的是 MongoDB Atlas(云)免费版或其 M2/M5 共享版集群,则需要使用 mongodb+srv 协议。在这种情况下,你可以在 pip 安装时指定 python -m pip install "pymongo[srv]"

可选地,如果你希望通过图形界面访问 MongoDB,可以从这里安装MongoDB Compass www.mongodb.com/products/tools/compass

我正在使用 MongoDB Compass 来创建数据库、集合并加载数据。在第五章,将时间序列数据持久化到数据库,你将学习如何使用 Python 创建数据库、集合并加载数据。

使用Compass选择创建数据库选项。对于数据库名称,可以输入 stock_data,对于集合名称,可以输入 microsoft。勾选时间序列复选框,并将 date 设置为时间字段

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file26.png

图 3.4 – MongoDB Compass 创建数据库界面

一旦数据库集合创建完成,点击导入数据并选择 datasets/Ch3/MSFT.csv 文件夹中的 MSFT 股票数据集。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file27.png

图 3.5 – MongoDB Compass 导入数据界面

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file28.png

图 3.6 – MongoDB Compass 在导入前审核数据类型界面

最终页面确认数据类型。最后,点击导入

如何操作……

在本教程中,你将连接到你已设置的 MongoDB 实例。如果你使用的是本地安装(本地安装或 Docker 容器),则连接字符串可能类似于mongodb://<username>:<password>@<host>:<port>/<DatabaseName>。如果你使用的是 Atlas,连接字符串可能更像是mongodb+srv://<username>:<password>@<clusterName>.mongodb.net/<DatabaseName>?retryWrites=true&w=majority

执行以下步骤:

  1. 首先,让我们导入必要的库:
import pandas as pd
from pymongo import MongoClient, uri_parser

建立与 MongoDB 的连接。对于自托管实例,例如本地安装,连接字符串可能是这样的:

# connecting to a self-hosted instance
url = "mongodb://127.0.0.1:27017"
client = MongoClient(url)
>>
MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True)

这等同于以下内容:

client = MongoClient(host=['127.0.0.1:27017'],
                     password=None,
                     username=None,
                     document_class=dict,
                     tz_aware=False,
                     connect=True)

如果你的自托管 MongoDB 实例具有用户名和密码,你必须提供这些信息。

uri_parser是一个有用的实用函数,允许你验证MongoDB 的 URL,如下所示:

uri_parser.parse_uri("mongodb://127.0.0.1:27107")
>>
{'nodelist': [('127.0.0.1', 27107)],
 'username': None,
 'password': None,
 'database': None,
 'collection': None,
 'options': {},
 'fqdn': None}

如果你连接的是MongoDB Atlas,那么你的连接字符串看起来应该像这样:

# connecting to Atlas cloud Cluster
cluster = 'cluster0'
username = 'user'
password = 'password'
database = 'stock_data'
url = \
f"mongodb+srv://{username}:{password}@{cluster}.3rncb.mongodb.net/{database}"
client =  MongoClient(url)
client
>>
MongoClient(host=['cluster0-shard-00-00.3rncb.mongodb.net:27017', 'cluster0-shard-00-01.3rncb.mongodb.net:27017', 'cluster0-shard-00-02.3rncb.mongodb.net:27017'], document_class=dict, tz_aware=False, connect=True, authsource='somesource', replicaset='Cluster0-shard-0', ssl=True)

在本章之前的教程中,我们使用了一个配置文件,例如database.cfg文件,用来存储我们的连接信息并隐藏凭证。你也应该遵循这个建议。

如果你的用户名或密码包含特殊字符,包括空格字符(:/?#[]@!$&'()* ,;=%),你需要对它们进行编码。你可以使用urllib Python 库中的quote_plus()函数进行百分号编码(百分号转义)。

这是一个示例:

username = urllib.parse.quote_plus('user!*@')

password = urllib.parse.quote_plus('pass/w@rd')

更多信息,请阅读这里的文档

  1. 一旦连接成功,你可以列出所有可用的数据库。在此示例中,我将数据库命名为stock_data,集合命名为microsoft
client.list_database_names()
>>
['admin', 'config', 'local', 'stock_data']
  1. 你可以使用list_collection_names列出stock_data数据库下可用的集合:
db = client['stock_data']
db.list_collection_names()
>>
['microsoft', 'system.buckets.microsoft', 'system.views']
  1. 现在,你可以指定查询哪个集合。在这个例子中,我们感兴趣的是名为microsoft的集合:
collection = db['microsoft']
  1. 现在,使用find方法将数据库查询到一个 pandas DataFrame 中:
results = collection.find({})
msft_df = (pd.DataFrame(results)
             .set_index('date')
             .drop(columns='_id'))
msft_df.head()
>>
               close         low    volume        high        open
date                                                               
2019-09-04  131.457260  130.358829  17995900  131.514567  131.142059
2019-09-05  133.768707  132.536556  26101800  134.083908  132.870864
2019-09-06  132.861359  132.001715  20824500  133.892908  133.749641
2019-09-09  131.352219  130.339762  25773900  133.482199  133.329371
2019-09-10  129.976837  128.477244  28903400  130.750506  130.664546

它是如何工作的……

第一步是连接到数据库,我们通过使用MongoClient创建 MongoDB 实例的客户端对象来实现。这将使你能够访问一组方法,如list_databases_names()list_databases(),以及其他属性,如addressHOST

MongoClient()接受一个连接字符串,该字符串应遵循 MongoDB 的 URI 格式,如下所示:

client = MongoClient("mongodb://localhost:27017")

另外,也可以通过显式提供host(字符串)和port(数字)位置参数来完成相同的操作,如下所示:

client = MongoClient('localhost', 27017)

主机字符串可以是主机名或 IP 地址,如下所示:

client = MongoClient('127.0.0.1', 27017)

请注意,要连接到使用默认端口(27017)的 localhost,你可以在不提供任何参数的情况下建立连接,如以下代码所示:

# using default values for host and port
client = MongoClient()

此外,你可以显式地提供命名参数,如下所示:

client = MongoClient(host='127.0.0.1',
                     port=27017,
                     password=password,
                     username=username,
                     document_class=dict,
                     tz_aware=False,
                     connect=True)

让我们来探讨这些参数:

  • host – 这可以是主机名、IP 地址或 MongoDB URI。它也可以是一个包含主机名的 Python 列表。

  • password – 你分配的密码。请参阅 Getting Ready 部分中关于特殊字符的说明。

  • username – 你分配的用户名。请参阅 Getting Ready 部分中关于特殊字符的说明。

  • document_class – 指定用于查询结果中文档的类。默认值是 dict

  • tz_aware – 指定 datetime 实例是否是时区感知的。默认值为 False,意味着它们是“天真”的(不具备时区感知)。

  • connect – 是否立即连接到 MongoDB 实例。默认值是 True

若需要更多参数,你可以参考官方文档页面 pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html

一旦与 MongoDB 实例建立连接,你可以指定使用的数据库,列出其集合,并查询任何可用的集合。在可以查询和检索文档之前的整体流程是:指定 数据库,选择你感兴趣的 集合,然后提交 查询

在前面的示例中,我们的数据库名为 stock_data,其中包含一个名为 microsoft 的集合。一个数据库可以包含多个集合,而一个集合可以包含多个文档。如果从关系型数据库的角度考虑,集合就像是表格,而文档代表表格中的行。

在 PyMongo 中,你可以使用不同的语法来指定数据库,如以下代码所示。请记住,所有这些语句都会生成一个 pymongo.database.Database 对象:

# Specifying the database
db = client['stock_data']
db = client.stock_data
db = client.get_database('stock_data')

在前面的代码中,get_database() 可以接受额外的参数,如 codec_optionsread_preferencewrite_concernread_concern,其中后两个参数更关注节点间的操作以及如何确定操作是否成功。

同样地,一旦你拥有了 PyMongo 数据库对象,你可以使用不同的语法来指定集合,如以下示例所示:

# Specifying the collection
collection = db.microsoft
collection = db['microsoft']
collection = db.get_collection('microsoft')

get_collection() 方法提供了额外的参数,类似于 get_database()

前面的三个语法变体返回一个 pymongo.database.Collection 对象,它带有额外的内建方法和属性,如 findfind_onefind_one_and_deletefind_one_and_replacefind_one_and_updateupdateupdate_oneupdate_manydelete_onedelete_many 等。

让我们探索不同的检索集合方法:

  • find() – 基于提交的查询从集合中检索多个文档。

  • find_one() – 基于提交的查询,从集合中检索单个文档。如果多个文档匹配,则返回第一个匹配的文档。

  • find_one_and_delete() – 查找单个文档,类似于find_one,但它会从集合中删除该文档,并返回删除的文档。

  • find_one_and_replace() - 查找单个文档并用新文档替换它,返回原始文档或替换后的文档。

  • find_one_and_update() - 查找单个文档并更新它,返回原始文档或更新后的文档。与find_one_and_replace不同,它是更新现有文档,而不是替换整个文档。

一旦你到达集合级别,你可以开始查询数据。在本示例中,你使用了find(),它类似于 SQL 中的SELECT语句。

如何实现…部分的步骤 5中,你查询了整个集合,通过以下代码检索所有文档:

collection.find({})

空字典{}find()中表示我们的过滤条件。当你传递一个空的过滤条件{}时,实际上是检索所有数据。这类似于 SQL 数据库中的SELECT *。另外,你也可以使用collection.find()来检索所有文档。

要在 MongoDB 中查询文档,你需要熟悉 MongoDB 查询语言(MQL)。通常,你会编写查询并将其传递给find方法,find方法类似于一个过滤器。

查询或过滤器采用键值对来返回匹配指定值的文档。以下是一个示例查询,查找收盘价大于 130 的股票:

query = {"close": {"$gt": 130}}
results = collection.find(query)

结果对象实际上是一个游标,它还没有包含结果集。你可以遍历游标或将其转换为 DataFrame。通常,当执行collection.find()时,它返回一个游标(更具体地说,是一个pymongo.cursor.Cursor对象)。这个游标对象只是查询结果集的指针,允许你遍历结果。你可以使用for循环或next()方法(可以类比为 Python 中的迭代器)。然而,在这个示例中,我们没有直接循环遍历游标对象,而是将整个结果集便捷地转换成了 pandas DataFrame。

这是一个将结果集检索到 pandas DataFrame 的示例。

df = pd.DataFrame(results)
df.info()
>>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1256 entries, 0 to 1255
Data columns (total 7 columns):
 #   Column  Non-Null Count  Dtype        
---  ------  --------------  -----        
 0   date    1256 non-null   datetime64[ns]
 1   close   1256 non-null   float64      
 2   _id     1256 non-null   object       
 3   low     1256 non-null   float64      
 4   volume  1256 non-null   int64        
 5   high    1256 non-null   float64      
 6   open    1256 non-null   float64      
dtypes: datetime64ns, float64(4), int64(1), object(1)
memory usage: 68.8+ KB

请注意,输出中添加了_id列,这是原始MSFT.csv文件中没有的。MongoDB 自动为集合中的每个文档添加了这个唯一标识符

在前面的代码中,查询作为过滤器,仅检索close值大于130的数据。PyMongo 允许你传递一个字典(键值对),指定要检索的字段。下面是一个示例:

query = {"close": {"$gt": 130}}
projection = {
   "_id": 0,
   "date":1,
   "close": 1,
   "volume": 1
}
results = collection.find(query, projection)
df = pd.DataFrame(results).set_index(keys='date')
print(df.head())
>>
               close    volume
date                           
2019-09-04  131.457260  17995900
2019-09-05  133.768707  26101800
2019-09-06  132.861359  20824500
2019-09-09  131.352219  25773900
2019-09-11  130.014984  24726100

在前面的代码中,我们指定了不返回_id,只返回dateclosevolume字段。

最后,在我们前面的例子中,注意查询中使用的$gt。它表示“大于”,更具体地说,它翻译为*“大于 130”*。在 MQL 中,操作符以美元符号$开头。以下是 MQL 中常用操作符的示例列表:

  • $eq - 匹配等于指定值的值

    • 示例:查询{"close": {"$eq": 130}}查找close字段值恰好为 130 的文档。
  • $gt - 匹配大于指定值的值

    • 示例:查询{"close": {"$gt": 130}}查找收盘价大于 130 的文档。
  • $gte - 匹配大于或等于指定值的值

    • 示例:查询{"close": {"$gte": 130}}查找收盘价大于或等于 130 的文档。
  • $lt - 匹配小于指定值的值

    • 示例:查询{"close": {"$lt": 130}}查找收盘价小于 130 的文档。
  • $lte - 匹配小于或等于指定值的值

    • 示例:查询{"close": {"$lt3": 130}}查找收盘价小于或等于 130 的文档。
  • $and - 使用逻辑AND操作符连接查询子句,所有条件必须为真。

    • 示例:查询{"$and": [{"close": {"$gt": 130}}, {"volume": {"$lt": 20000000}}]}查找收盘价大于 130交易量小于 20,000,000 的文档。
  • $or - 使用逻辑OR操作符连接查询子句,至少有一个条件必须为真。

    • 示例:查询{"$or": [{"close": {"$gt": 135}}, {"volume": {"$gt": 30000000}}]}查找收盘价大于 135交易量大于 30,000,000 的文档。
  • $in - 匹配数组(列表)中指定的值

    • 示例:查询{"date": {"$in": [datetime.datetime(2019, 9, 4), datetime.datetime(2019, 9, 5), datetime.datetime(2019, 9, 6)]}}查找日期字段与以下指定日期之一匹配的文档:2019 年 9 月 4 日;2019 年 9 月 5 日;2019 年 9 月 6 日。

要查看 MQL 中操作符的完整列表,您可以访问官方文档:www.mongodb.com/docs/manual/reference/operator/query/

还有更多……

有多种方法可以使用PyMongo从 MongoDB 中检索数据。在前面的部分,我们使用了db.collection.find(),它总是返回一个游标。正如我们之前讨论的,find()返回的是指定集合中所有匹配的文档。如果您只想返回匹配文档的第一个实例,那么db.collection.find_one()将是最佳选择,它会返回一个字典对象,而不是游标。请记住,这只会返回一个文档,示例如下:

db.microsoft.find_one()
>>>
{'date': datetime.datetime(2019, 9, 4, 0, 0),
 'close': 131.45726013183594,
 '_id': ObjectId('66e30c09a07d56b6db2f446e'),
 'low': 130.35882921332006,
 'volume': 17995900,
 'high': 131.5145667829114,
 'open': 131.14205897649285}

在处理游标时,您有多种方法可以遍历数据:

  • 使用 pd.DataFrame(cursor) 将数据转换为 pandas DataFrame,如以下代码所示:
cursor = db.microsoft.find()
df = pd.DataFrame(cursor)
  • 转换为 Python listtuple
data = list(db.microsoft.find())

您还可以将 Cursor 对象转换为 Python 列表,然后将其转换为 pandas DataFrame,像这样:

data = list(db.microsoft.find())
df = pd.DataFrame(data)
  • 使用 next() 将指针移动到结果集中的下一个项目:
cursor = db.microsoft.find()
cursor.next()
  • 通过对象进行循环,例如,使用 for 循环:
cursor = db.microsoft.find()
for doc in cursor:
    print(doc)

前面的代码将遍历整个结果集。如果您想遍历前 5 条记录,可以使用以下代码:

cursor = db.microsoft.find()
for doc in cursor[0:5]:
    print(doc)
  • 指定 index。在这里,我们打印第一个值:
cursor = db.microsoft.find()
cursor[0]

请注意,如果提供了一个切片,例如 cursor[0:1],这是一个范围,那么它将返回一个游标对象(而不是文档)。

另见

欲了解更多有关 PyMongo API 的信息,请参考官方文档,您可以在此找到:pymongo.readthedocs.io/en/stable/index.html

从时间序列数据库读取数据

时间序列数据库,一种 NoSQL 数据库,专为时间戳或时间序列数据进行了优化,并在处理包含 IoT 数据或传感器数据的大型数据集时提供了更好的性能。过去,时间序列数据库的常见使用场景主要与金融股票数据相关,但它们的应用领域已经扩展到其他学科和领域。在本教程中,您将探索三种流行的时间序列数据库:InfluxDBTimescaleDBTDEngine

InfluxDB 是一个流行的开源时间序列数据库,拥有一个庞大的社区基础。在本教程中,我们将使用本文写作时的 InfluxDB 最新版本,即 2.7.10 版本。最近的 InfluxDB 版本引入了 Flux 数据脚本语言,您将通过 Python API 使用该语言查询我们的时间序列数据。

TimescaleDB 是 PostgreSQL 的扩展,专门为时间序列数据进行了优化。它利用 PostgreSQL 的强大功能和灵活性,同时提供了额外的功能,专门为高效处理带时间戳的信息而设计。使用 TimescaleDB 的一个优势是,您可以利用 SQL 查询数据。TimescaleDB 是一个开源的时间序列数据库,在本教程中,我们将使用 TimescaleDB 最新的版本 2.16.1。

TDEngine 是一个开源的时间序列数据库,专为物联网(IoT)、大数据和实时分析设计。与 TimescaleDB 类似,TDEngine 使用 SQL 查询数据。在本教程中,我们将使用 TDEngine 的最新版本 3.3.2.0。

准备工作

本食谱假设您可以访问正在运行的 InfluxDB、TimeseriesDB 或 TDEngine 实例。您将安装适当的库来使用 Python 连接和与这些数据库交互。对于InfluxDB V2,您需要安装 influxdb-client;对于TimescaleDB,您需要安装 PostgreSQL Python 库 psycopg2(回想在本章的从关系数据库读取数据食谱中,我们使用了 psycopg3);最后,对于TDEngine,您需要安装 taospy

您可以通过以下方式使用 pip 安装这些库:

pip install 'influxdb-client[ciso]'
pip install 'taospy[ws]'
pip install psycopg2

要使用 conda 安装,请使用以下命令:

conda install -c conda-forge influxdb-client
conda install -c conda-forge taospy taospyws
conda install -c conda-forge psycopg2

如果您没有访问这些数据库的权限,那么最快速的方式是通过 Docker。以下是 InlfuxDB、TimescaleDB 和 TDEngine 的示例命令:

InfluxDB Docker 容器

要创建 InfluxDB 容器,您需要运行以下命令:

docker run -d\
    --name influxdb-ch3 \
    -p 8086:8086 \
    influxdb:2.7.9-alpine

欲了解更多信息,您可以访问官方 Docker Hub 页面 hub.docker.com/_/influxdb

一旦influxdb-ch3容器启动并运行,您可以使用您喜欢的浏览器导航到 http://localhost:8086,并继续设置,例如用户名、密码、初始组织名称和初始存储桶名称。

对于本食谱,我们将使用国家海洋和大气管理局NOAA)的水位样本数据,时间范围为 2019 年 8 月 17 日至 2019 年 9 月 17 日,数据来源为圣塔莫尼卡和科约特溪。

在数据浏览器 UI 中,您可以运行以下Flux查询来加载样本数据集:

import "influxdata/influxdb/sample"
sample.data(set: "noaaWater")
    |> to(bucket: "tscookbook")

在之前的代码片段中,NOAA 数据集已加载到初始设置时创建的 tscookbook 存储桶中。

关于如何加载样本数据或其他提供的样本数据集的说明,请参阅 InfluxDB 官方文档 docs.influxdata.com/influxdb/v2/reference/sample-data/

TimescaleDB Docker 容器

要创建 TimescaleDB 容器,您需要运行以下命令:

docker run -d \
    --name timescaledb-ch3 \
    -p 5432:5432 \
    -e POSTGRES_PASSWORD=password \
    timescale/timescaledb:latest-pg16

欲了解更多信息,您可以访问官方 Docker Hub 页面 hub.docker.com/r/timescale/timescaledb

一旦timescaledb-ch3容器启动并运行,您可以使用与从关系数据库读取数据食谱中的准备工作部分相同的说明加载 MSFT.csv 文件。

注意,默认的用户名是 postgres,密码是您在 Docker 命令中设置的密码。

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file29.png

图 3. – DBeaver TimescaleDB/Postgres 连接设置(应该与图 3.1 类似)

由于 TimescaleDB 基于 PostgreSQL,它也默认使用 5432 端口。因此,如果你已经在本地运行一个默认使用 5432 端口的 PostgreSQL 数据库,你可能会遇到 TimescaleDB 的端口冲突问题。在这种情况下,你可以选择修改 Docker 运行配置并更改端口。

TDEngine Docker 容器 [待删除部分]

要创建一个 TDEngine 容器,你需要运行以下命令:

docker run -d \
   --name tdengine-ch3 \
   -p 6030-6060:6030-6060 \
   -p 6030-6060:6030-6060/udp \
   tdengine/tdengine:3.3.2.0

更多信息可以访问官方的 Docker Hub 页面 hub.docker.com/r/tdengine/tdengine

一旦 tdengine-ch3 容器启动并运行,你可以通过在容器 shell 中运行 taosBenchmark 命令来创建一个演示数据集。以下是从正在运行的容器内部访问 shell 并运行所需命令来安装和设置演示数据集的步骤:

docker exec -it tdengine-ch3 /bin/bash
>>
root@9999897cbeb4:~# taosBenchmark

一旦演示数据集创建完成,你可以退出终端。现在你可以使用 DBeaver 验证数据是否已创建。你可以使用与 从关系数据库读取数据 食谱中 准备工作 部分相同的说明。

请注意,默认用户名是 root,默认密码是 taosdata

https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file30.png

图 3. – DBeaver TDEngine 连接设置

现在你应该能看到一个名为 test数据库 被创建,并且一个名为 meters超级表,它包含 10,000 个 子表,命名为 d0d9999,每个表包含大约 10,000 行和四列(tscurrentvoltagephase)。你可能无法在 DBeaver 导航窗格中看到 meters 超级表,但如果你运行以下 SQL 查询 “SELECT COUNT(*) FROM test.meters;”,它应该会输出 100,000,000 行(10,000 个子表乘以每个子表的 10,000 行)。

如何做到这一点…

本食谱将演示如何连接并与三种流行的时序数据库系统进行交互。

InfluxDB

我们将利用 Influxdb_client Python SDK 来访问 InfluxDB 2.x,它支持 pandas DataFrame 进行读取和写入功能。让我们开始吧:

  1. 首先,让我们导入必要的库:
from influxdb_client import InfluxDBClient
import pandas as pd
  1. 要使用 InfluxDBClient(url="http://localhost:8086", token=token) 建立连接,你需要定义 tokenorgbucket 变量:
token = "c5c0JUoz-\
joisPCttI6hy8aLccEyaflyfNj1S_Kff34N_4moiCQacH8BLbLzFu4qWTP8ibSk3JNYtv9zlUwxeA=="
org = "ts"
bucket = "tscookbook"

可以将桶看作是关系数据库中的数据库。

  1. 现在,你可以通过将 urltokenorg 参数传递给 InlfuxDBClient() 来建立连接:
client = InfluxDBClient(url="http://localhost:8086",
                        token=token,
                        org=org)
  1. 接下来,你将实例化 query_api
query_api = client.query_api()
  1. 传递你的 Flux 查询,并使用 query_data_frame 方法请求以 pandas DataFrame 格式返回结果:
query = f'''
from(bucket: "tscookbook")
  |> range(start: 2019-09-01T00:00:00Z)
  |> filter(fn: (r) =>
        r._measurement == "h2o_temperature" and
        r.location == "coyote_creek" and
        r._field == "degrees"
  )
  |> movingAverage(n: 120)
  |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
'''
result = client.query_api().query_data_frame(org=org, query=query)
  1. 在前面的 Flux 脚本中,选择了度量 h2o_temparature,并且位置是 coyote_creek。现在让我们检查一下 DataFrame。请注意以下输出中的数据类型:
result.info()
>>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3885 entries, 0 to 3884
Data columns (total 8 columns):
 #   Column        Non-Null Count  Dtype             
---  ------        --------------  -----             
 0   result        3885 non-null   object            
 1   table         3885 non-null   int64             
 2   _start        3885 non-null   datetime64[ns, UTC]
 3   _stop         3885 non-null   datetime64[ns, UTC]
 4   _time         3885 non-null   datetime64[ns, UTC]
 5   _measurement  3885 non-null   object            
 6   location      3885 non-null   object            
 7   degrees       3885 non-null   float64           
dtypes: datetime64ns, UTC, float64(1), int64(1), object(3)
memory usage: 242.9+ KB
  1. 如果你只想检索时间和温度列,你可以更新 Flux 查询,如下所示:
query = f'''
from(bucket: "tscookbook")
  |> range(start: 2019-09-01T00:00:00Z)
  |> filter(fn: (r) =>
        r._measurement == "h2o_temperature" and
        r.location == "coyote_creek" and
        r._field == "degrees"
  )
  |> movingAverage(n: 120)
  |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
  |> keep(columns: ["_time", "degrees"])
'''
result = client.query_api().query_data_frame( query=query)
result.head()
>>
result  table                     _time    degrees
0  _result      0 2019-09-01 11:54:00+00:00  64.891667
1  _result      0 2019-09-01 12:00:00+00:00  64.891667
2  _result      0 2019-09-01 12:06:00+00:00  64.816667
3  _result      0 2019-09-01 12:12:00+00:00  64.841667
4  _result      0 2019-09-01 12:18:00+00:00  64.850000

原始数据集包含 15,258 条观察数据,数据每 6 分钟收集一次,来源于两个站点(位置)。移动平均是基于 120 个数据点计算的。理解数据集的渐进性非常重要。最终的 DataFrame 包含 3885 条记录。

TimescaleDB

由于 TimescaleDB 基于 PostgreSQL,并且我们已经安装了 psycopg2,因此检索和查询数据的方式应该与示例 从关系数据库中读取数据 中使用的方法类似。

这里简要说明如何使用 pandas 的 from_sql 来实现:

  1. 导入 SQLAlchemy 和 pandas
import pandas as pd
from sqlalchemy import create_engine
  1. 使用正确的连接字符串创建 PostgreSQL 后端的引擎对象。
engine =\
    create_engine("postgresql+psycopg2://postgres:password@localhost:5432/postgres")
  1. 最后,使用 read_sql 方法将查询结果集检索到 pandas DataFrame 中:
query = "SELECT * FROM msft"
df = pd.read_sql(query,
                 engine,
                 index_col='date',
                 parse_dates={'date': '%Y-%m-%d'})
print(df.head())

TimescaleDB 提供了比 PostgreSQL 更多的优势,你将在 第五章 持久化时间序列数据到数据库 中探索其中的一些优势。然而,查询 TimescaleDB 的体验与熟悉 SQL 和 PostgreSQL 的用户类似。

TDEngine

对于这个示例,让我们更新配置文件 database.cfg,根据 技术要求 包括一个 [TDENGINE] 部分,如下所示:

[TDENGINE]
user=root
password=taosdata
url=http://localhost:6041

你将首先建立与 TDEngine 服务器的连接,然后对 taosBenchmark 演示数据集进行查询,该数据集在 Getting Read 部分中有所描述。

  1. 从导入所需的库开始。
import taosrest
import pandas as pd
  1. 你将创建一个 Python 字典,存储所有连接数据库所需的参数值,如 urluserpassword
import configparser
config = configparser.ConfigParser()
config.read('database.cfg')
params = dict(config['TDENGINE'])
  1. 建立与服务器的连接。
conn = taosrest.connect(**params)
  1. 运行以下查询,并使用连接对象 conn 的 query 方法执行该查询。
query = """
SELECT *
FROM test.meters
WHERE location = 'California.LosAngles'
LIMIT 100000;
"""
results = conn.query(query)
  1. 你可以验证结果集中的行数和列名。
results.rows
>>
100000
results.fields
>>
[{'name': 'ts', 'type': 'TIMESTAMP', 'bytes': 8},
 {'name': 'current', 'type': 'FLOAT', 'bytes': 4},
 {'name': 'voltage', 'type': 'INT', 'bytes': 4},
 {'name': 'phase', 'type': 'FLOAT', 'bytes': 4},
 {'name': 'groupid', 'type': 'INT', 'bytes': 4},
 {'name': 'location', 'type': 'VARCHAR', 'bytes': 24}]
  1. results.data 包含结果集中的值,但没有列标题。在将结果集写入 pandas DataFrame 之前,我们需要从 results.fields 捕获列名列表:
cols = [col['name'] for col in results.fields ]
df = pd.DataFrame(results.data, columns=cols)
df = df.set_index('ts')
df.info()
>>
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 100000 entries, 2017-07-14 05:40:00 to 2017-07-14 05:40:05.903000
Data columns (total 5 columns):
 #   Column    Non-Null Count   Dtype 
---  ------    --------------   ----- 
 0   current   100000 non-null  float64
 1   voltage   100000 non-null  int64 
 2   phase     100000 non-null  float64
 3   groupid   100000 non-null  int64 
 4   location  100000 non-null  object
dtypes: float64(2), int64(2), object(1)
memory usage: 4.6+ MB

它是如何工作的……

TimescaleDB 和 TDEngine 都使用 SQL 来查询数据,而 InfluxDB 使用他们的专有查询语言 Flux。

InfluxDB 1.8x 引入了 Flux 查询语言,作为 InfluxQL 的替代查询语言,后者与 SQL 更加相似。InfluxDB 2.0 引入了 bucket 的概念,数据存储在这里,而 InfluxDB 1.x 将数据存储在数据库中。

在这个示例中,我们从创建一个 InfluxDbClient 实例开始,这样我们就可以访问 query_api 方法,进而获得包括以下方法:

  • query() 返回结果作为 FluxTable

  • query_csv() 返回结果作为 CSV 迭代器(CSV 读取器)。

  • query_data_frame() 返回结果作为 pandas DataFrame。

  • query_data_frame_stream() 返回一个 pandas DataFrame 流作为生成器。

  • query_raw() 返回原始未处理的数据,格式为 s 字符串。

  • query_stream()类似于query_data_frame_stream,但它返回的是一个生成器流,其中包含FluxRecord

在这个示例中,你使用了client.query_api()来获取数据,如下所示:

result = client.query_api().query_data_frame(org=org, query=query)

你使用了query_data_frame,它执行一个同步的 Flux 查询并返回一个你熟悉的 pandas DataFrame。

请注意,我们在 Flux 查询中必须使用pivot函数来将结果转换为适合 pandas DataFrame 的表格格式。

pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")

让我们逐行分析前面的代码:

pivot()用于重塑数据,并将其从长格式转换为宽格式。

rowKey参数指定了哪一列用作每行的唯一标识符。在我们的示例中,我们指定了["_time"],因此每行将有一个唯一的时间戳。

columnKey参数指定了哪一列的值将用于在输出中创建新列。在我们的示例中,我们指定了["_field"]来从字段名称创建列。

valueColumn参数指定了哪个列包含数值,我们指定了"_value"来填充新列中的相应值。

还有更多…

在使用InfluxDBinfluxdb-client时,有一个额外的参数可以用来创建 DataFrame 索引。在query_data_frame()中,你可以将一个列表作为data_frame_index参数的参数传入,如下面的示例所示:

result =\
query_api.query_data_frame(query=query,                  
                                    data_frame_index=['_time'])
result['_value'].head()
>>
_time
2021-04-01 01:45:02.350669+00:00    64.983333
2021-04-01 01:51:02.350669+00:00    64.975000
2021-04-01 01:57:02.350669+00:00    64.916667
2021-04-01 02:03:02.350669+00:00    64.933333
2021-04-01 02:09:02.350669+00:00    64.958333
Name: _value, dtype: float64

这将返回一个带有DatetimeIndex_time)的时间序列 DataFrame。

另见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值