原文:
annas-archive.org/md5/7277c6f80442eb633bdbaf16dcd96fad
译者:飞龙
第一章:1 开始时间序列分析
加入我们在 Discord 上的书籍社区
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file0.png
当你开始学习Python编程时,你经常会按照指示安装包并导入库,然后进入一段跟着代码走的学习流程。然而,在任何数据分析或数据科学过程中,一个常常被忽视的部分就是确保正确的开发环境已经搭建好。因此,从一开始就打好基础是至关重要的,这样可以避免未来可能出现的麻烦,比如实现过于复杂、包冲突或依赖危机。搭建好合适的开发环境会在你完成项目时帮助你,确保你能够以可重现和生产就绪的方式交付成果。
这样的主题可能不会那么有趣,甚至可能会感觉有些行政负担,而不是直接进入核心主题或当前的项目。但正是这个基础,才将一个经验丰富的开发者与其他人区分开来。就像任何项目一样,无论是机器学习项目、数据可视化项目,还是数据集成项目,一切都始于规划,并确保在开始核心开发之前,所有需要的部分都已经到位。
本章中,你将学习如何设置Python 虚拟环境,我们将介绍两种常见的方法来实现这一点。步骤将涵盖常用的环境和包管理工具。本章旨在实践操作,避免过多的行话,并让你以迭代和有趣的方式开始创建虚拟环境。
随着本书的进展,你将需要安装多个特定于时间序列分析、时间序列可视化、机器学习和深度学习(针对时间序列数据)的 Python 库。不管你有多大的诱惑跳过这一章,都不建议这么做,因为它将帮助你为随后的任何代码开发打下坚实的基础。在这一章结束时,你将掌握使用conda或venv创建和管理 Python 虚拟环境所需的技能。
本章将涵盖以下内容:
-
开发环境设置
-
安装 Python 库
-
安装 JupyterLab 和 JupyterLab 扩展
技术要求
本章中,你将主要使用命令行。对于 macOS 和 Linux,默认的终端将是 (bash
或 zsh
),而在 Windows 操作系统中,你将使用Anaconda 提示符,这是 Anaconda 或 Miniconda 安装包的一部分。关于如何安装 Anaconda 或 Miniconda,将在随后的准备工作部分讨论。
我们将使用 Visual Studio Code 作为 IDE,它可以免费获取,网址为 code.visualstudio.com
。它支持 Linux、Windows 和 macOS。
其他有效的替代选项也可以让你跟随学习,具体包括:
-
Sublime Text,网址为 https://www.sublimetext.com
-
Spyder,网址为
www.spyder-ide.org
-
PyCharm Community Edition,网址为
www.jetbrains.com/pycharm/download/
-
Jupyter Notebook,网址为
jupyter.org
本章的源代码可以在 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 环境,在进行所需的升级并进行测试之后,再决定是否继续进行升级。这正是环境管理器(conda
或 venv
)和包管理器(conda
或 pip
)在你的开发和生产部署过程中所带来的价值。
准备工作
在本节中,假设你已经通过以下任一方式安装了最新版本的 Python:
-
推荐的方法是通过像 Anaconda(
www.anaconda.com/products/distribution
)这样的 Python 发行版来安装,Anaconda 自带所有必需的包,并支持 Windows、Linux 和 macOS(2022.05 版本起支持 M1)。另外,你也可以安装 Miniconda(docs.conda.io/en/latest/miniconda.html
)或 Miniforge(github.com/conda-forge/miniforge
)。 -
直接从官方 Python 网站下载安装程序:
www.python.org/downloads/
。 -
如果你熟悉Docker,你可以下载官方的 Python 镜像。你可以访问 Docker Hub 来确定要拉取的镜像:
hub.docker.com/_/python
。同样,Anaconda 和 Miniconda 可以通过遵循官方说明与 Docker 一起使用,说明请见此处:docs.anaconda.com/anaconda/user-guide/tasks/docker/
截至撰写时,最新的 Python 版本是 Python 3.11.3。
Anaconda 支持的最新 Python 版本
Anaconda 的最新版本是 2023.03,于 2023 年 4 月发布。默认情况下,Anaconda 会将 Python 3.10.9 作为基础解释器。此外,你可以使用
conda create
创建一个 Python 版本为 3.11.3 的虚拟环境,稍后你将在本食谱中看到如何操作。
获取快速顺利启动的最简单有效方法是使用像Anaconda或Miniconda这样的 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 Prompt 或 Anaconda 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 模块,提供环境管理,无需额外安装。
conda
和venv
都允许你为你的 Python 项目创建多个虚拟环境,这些项目可能需要不同版本的 Python 解释器(例如,3.4、3.8 或 3.9)或不同的 Python 包。此外,你可以创建一个沙箱虚拟环境来尝试新的包,以了解它们如何工作,而不会影响你基础的 Python 安装。
为每个项目创建一个单独的虚拟环境是许多开发者和数据科学实践者采纳的最佳实践。遵循这一建议从长远来看会对你有所帮助,避免在安装包时遇到常见问题,如包依赖冲突。
使用 Conda
首先打开你的终端(Windows 的 Anaconda 提示符):
- 首先,让我们确认你拥有最新版本的
conda
。你可以通过以下命令来做到这一点:
$ conda update conda
上面的代码将更新 conda 包管理器。如果你使用的是现有的安装,这将非常有用。通过这种方式,你可以确保拥有最新版本。
- 如果你已经安装了 Anaconda,可以使用以下命令更新到最新版本:
$ conda update anaconda
- 现在,你将创建一个名为
py310
的新虚拟环境,指定的 Python 版本为 Python 3.10:
$ conda create -n py310 python=3.10
在这里,-n
是--name
的快捷方式。
-
conda
可能会识别出需要下载和安装的其他包。系统可能会提示你是否继续。输入y
然后按Enter键继续。 -
你可以通过添加
-y
选项跳过前面步骤中的确认消息。如果你对自己的操作有信心,并且不需要确认消息,可以使用此选项,让conda
立即继续而不提示你进行响应。你可以通过添加-y
或--yes
选项来更新你的命令,如以下代码所示:
$ conda create -n py10 python=3.10 -y
- 一旦设置完成,你就可以激活新的环境。激活一个 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
环境,而不是我们新创建的虚拟环境。
- 现在,激活你新的
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
- 另一种确认我们新虚拟环境是活动环境的方法是运行以下命令:
$ 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 虚拟环境列表
- 一旦激活了特定环境,你安装的任何软件包将只会在该隔离环境中可用。例如,假设我们安装 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
-
一旦你按下 y 并按 Enter 键,
conda
将开始下载并安装这些软件包。 -
一旦你完成在当前
py310
环境中的工作,你可以使用以下命令deactivate
来退出并返回到基础 Python 环境:
$ conda deactivate
- 如果你不再需要 py310 环境并希望删除它,你可以使用
env remove
命令来删除它。该命令将完全删除该环境及所有已安装的库。换句话说,它将删除(移除)该环境的整个文件夹:
$ conda env remove -n py310
使用 venv
一旦安装了 Python 3x,你可以使用内置的venv
模块,该模块允许你创建虚拟环境(类似于conda
)。注意,当使用venv
时,你需要提供一个路径,以指定虚拟环境(文件夹)创建的位置。如果没有提供路径,虚拟环境将会在你运行命令的当前目录下创建。在下面的代码中,我们将在Desktop
目录中创建虚拟环境。
按照以下步骤创建新环境、安装包并使用venv
删除该环境:
- 首先,决定你希望将新的虚拟环境放在哪里,并指定路径。在这个示例中,我已经导航到
Desktop
并运行了以下命令:
$ cd Desktop
$ python -m venv py310
上面的代码将在Desktop
目录中创建一个新的 py310 文件夹。py310
文件夹包含若干子目录、Python 解释器、标准库以及其他支持文件。该文件夹结构类似于conda
在envs
目录中创建的环境文件夹。
- 让我们激活 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.bat
和Activate.ps1
,后者应在 Anaconda PowerShell Prompt 中使用,而不是 Anaconda Windows 命令提示符。通常在 PowerShell 中,如果你省略文件扩展名,正确的脚本会被执行。但最好是指定正确的文件扩展名,例如指定Activate.ps1
,如下所示:
.\py310\Scripts\Activate.ps1
- 现在,让我们使用以下命令检查已安装的版本:
$ python --version
> Python 3.10.10
- 一旦完成使用py310环境进行开发,你可以使用deactivate命令将其停用,返回到基础 Python 环境:
$ deactivate
- 如果你不再需要
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
您在激活虚拟环境后安装的任何额外包或库都将与其他环境隔离,并保存在该环境的文件夹结构中。
如果我们比较venv
和conda
的文件夹结构,您会看到相似之处,如下图所示:
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 等。
还有更多…
在前面的示例中,你可以使用conda
或venv
从零开始创建 Python 虚拟环境。你创建的虚拟环境可能还不包含所需的包,因此你需要特别为项目安装这些包。你将会在接下来的食谱“安装 Python 库”中了解如何安装包。
还有其他方式可以在conda
中创建虚拟环境,我们将在这里讨论这些方式。
使用 YAML 文件创建虚拟环境
你可以从YAML文件创建虚拟环境。这个选项可以让你在一步中定义环境的多个方面,包括应该安装的所有包。
你可以在 VSCode 中创建一个 YAML 文件。以下是一个env.yml
文件的示例,使用 Python 3.10 创建一个名为tscookbook
的conda
环境:
# 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 文件。如果你想与他人分享环境配置或为将来使用创建备份,这个方法非常有用。以下三个命令将使用稍微不同的语法选项,达到将py310
的conda
环境导出为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 文件创建新环境相同的效果。以下示例将把py310
的conda
环境克隆为一个名为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 代码,这些笔记本已经预装了一些最受欢迎的数据科学包,包括
pandas
、statsmodels
、scikit-learn
和TensorFlow
。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.txt
、environment_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
首先,让我们测试一下venv
和pip
。运行以下脚本(我在 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
为我们识别和安装的pandas
和matplotlib
的依赖关系。
现在,让我们这次使用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 云安装包。 -
pip
从 Python 包索引(PyPI) 仓库安装包。 -
conda
会对它计划下载的所有包进行非常彻底的分析,并且在处理版本冲突时比pip
做得更好。
引导文件
第二个选项是从现有环境中生成 requirements.txt
文件。当您为将来的使用重建环境,或与他人共享包和依赖关系列表时,这非常有用,可以确保可重现性和一致性。假设您在一个项目中工作并安装了特定的库,您希望确保在共享代码时,其他用户能够安装相同的库。这时,生成 requirements.txt
文件就显得非常方便。同样,导出 YAML 环境配置文件的选项之前已经展示过。
让我们看看如何在pip
和conda
中执行此操作。请记住,两种方法都会导出已经安装的包及其当前版本的列表。
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
另请参见
-
要查找 Anaconda 中所有可用的包,您可以访问
docs.anaconda.com/anaconda/packages/pkg-docs/
。 -
要在 PyPI 仓库中搜索包,您可以访问
pypi.org/
。
安装 JupyterLab 和 JupyterLab 扩展
在本书中,你可以使用自己喜欢的 Python IDE(例如 PyCharm 或 Spyder)或文本编辑器(例如 Visual Studio Code、Atom 或 Sublime)进行操作。另一种选择是基于笔记本概念的交互式学习,它通过 Web 界面进行。更具体地说,Jupyter Notebook 或 Jupyter Lab 是学习、实验和跟随本书教程的首选方法。有趣的是,Jupyter 这个名字来源于三种编程语言:Julia、Python 和 R。或者,你也可以使用 Google 的 Colab 或 Kaggle Notebooks。欲了解更多信息,请参考本章 开发环境设置 教程中的 另见 部分。如果你不熟悉 Jupyter Notebooks,可以在这里了解更多信息:jupyter.org/
。
在本教程中,你将安装 Jupyter Notebook、JupyterLab 以及额外的 JupyterLab 扩展。
此外,你还将学习如何安装单个软件包,而不是像前面教程中那样采用批量安装方法。
在后续示例中使用 CONDA
在后续的操作中,当新环境被创建时,代码将使用 conda 进行编写。前面的教程已经涵盖了创建虚拟环境的两种不同方法(venv 与 conda)以及安装软件包的两种方法(pip 与 conda),这将允许你根据自己的选择继续操作。
准备就绪
我们将创建一个新环境并安装本章所需的主要软件包,主要是 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:
- 现在我们已经激活了环境,可以使用
conda
install
安装conda create
时未包含的其他软件包:
$ conda install jupyter -y
- 你可以通过输入以下命令启动 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 服务器
-
要终止 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 网络服务器
-
现在,你可以安全地关闭浏览器。
-
请注意,在前面的例子中,当 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 浏览器。
- 如果你不希望系统自动启动浏览器,可以使用以下代码:
$ 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
- 最后,如果默认的
port 8888
端口正在使用中,或者你希望更改端口号,可以添加-p
并指定你想要的端口号,如下例所示。在这里,我指示网络服务器使用port 8890
:
$ jupyter lab --browser=chrome --port 8890
这将启动 Chrome 并指向localhost:8890/lab
。
请注意,当 JupyterLab 启动时,你只会在笔记本/控制台部分看到一个内核。这是基础 Python 内核。我们原本预期看到两个内核,分别反映我们拥有的两个环境:基础环境和timeseries
虚拟环境。让我们使用以下命令检查我们有多少个虚拟环境:
-
下图显示了 JupyterLab 界面,只有一个内核,属于基础环境:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file9.jpg
图 1.8:JupyterLab 界面,只显示一个内核,该内核属于基础环境
-
下图显示了两个 Python 环境:
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file10.jpg
图 1.9:显示两个 Python 环境
我们可以看到 timeseries 虚拟环境是活动的。
- 你需要为新的
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
- 我们可以使用以下命令检查 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 代码。
- 现在,你可以重新启动 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 notebook
或 jupyter 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
另见
-
要了解更多关于 JupyterLab 扩展的信息,请参阅官方文档:
jupyterlab.readthedocs.io/en/stable/user/extensions.html
。 -
如果你想了解更多关于如何创建 JupyterLab 扩展的示例演示,请参考官方 GitHub 仓库:
github.com/jupyterlab/extension-examples
。 -
在第 9 步中,我们手动安装了
kernelspec
文件,这为 Jupyter 和我们的conda
环境之间创建了映射。这个过程可以通过nb_conda
自动化。关于nb_conda
项目的更多信息,请参考官方 GitHub 仓库:github.com/Anaconda-Platform/nb_conda
。
第二章:2 从文件读取时间序列数据
加入我们的 Discord 书籍社区
https://github.com/OpenDocCN/freelearn-ds-pt4-zh/raw/master/docs/ts-anal-py-cb-2e/img/file0.png
在本章中,我们将使用 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
中的一些可用参数:
- 首先,加载所需的库:
import pandas as pd
from pathlib import Path
- 为文件位置创建一个
Path
对象:
filepath =\
Path('../../datasets/Ch2/movieboxoffice.csv')
- 使用
read_csv
函数将 CSV 文件读取到 DataFrame 中,并传递包含额外参数的filepath
。
CSV 文件的第一列包含电影发布日期,需要将其设置为DatetimeIndex
类型的索引(index_col=0
和parse_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 的前五行
- 打印 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
- 注意,
Date
列现在是一个索引(而非列),类型为DatetimeIndex
。另外,Daily
和Forecast
列的 dtype 推断错误。你本来期望它们是float
类型。问题在于源 CSV 文件中的这两列包含了美元符号 ($
) 和千位分隔符(,
)。这些非数字字符会导致列被解释为字符串。具有dtype
为object
的列表示该列包含字符串或混合类型的数据(不是同质的)。
要解决这个问题,你需要去除美元符号 ($
) 和千位分隔符(,
)或任何其他非数字字符。你可以使用 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,并且 Daily
和 Forecast
列的 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=0
和 parse_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
表示月份中的日期,例如01
或02
。 -
%b
表示缩写的月份名称,例如Apr
或May
。 -
%Y
表示四位数的年份,例如2020
或2021
。
其他常见的字符串代码包括以下内容:
-
%y
表示两位数的年份,例如19
或20
。 -
%B
表示月份的全名,例如January
或February
。 -
%m
表示月份,作为两位数,例如01
或02
。
有关 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 的库(引擎)包括 xlrd
、openpyxl
、odf
和 pyxlsb
。处理 Excel 文件时,最常用的两个库通常是 xlrd
和 openpyxl
。
xlrd
库只支持 .xls
文件。因此,如果你正在处理较旧的 Excel 格式,例如 .xls
,那么 xlrd
就能很好地工作。对于更新的 Excel 格式,例如 .xlsx
,我们需要使用不同的引擎,在这种情况下,推荐使用 openpyxl
。
要使用 conda
安装 openpyxl
,请在终端运行以下命令:
>>> conda install openpyxl
要使用 pip
安装,请运行以下命令:
>>> pip install openpyxl
我们将使用 sales_trx_data.xlsx
文件,你可以从本书的 GitHub 仓库下载。请参阅本章的 技术要求 部分。该文件包含按年份拆分的销售数据,分别存在两个工作表中(2017
和 2018
)。
如何操作…
你将使用 pandas 和 openpyxl
导入 Excel 文件(.xlsx
),并利用 read_excel()
中的一些可用参数:
- 导入此配方所需的库:
import pandas as pd
from pathlib import Path
filepath = \
Path('../../datasets/Ch2/sales_trx_data.xlsx')
- 使用
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"]
。
在以下代码中,你将使用工作表位置来读取第一个和第二个工作表(0
和 1
索引)。这将返回一个 Python dictionary
对象,包含两个 DataFrame。请注意,返回的字典(键值对)具有数字键(0
和 1
),分别表示第一个和第二个工作表(位置索引):
ts = pd.read_excel(filepath,
engine='openpyxl',
index_col=1,
sheet_name=[0,1],
parse_dates=True)
ts.keys()
>> dict_keys([0, 1])
- 或者,你可以传递一个工作表名称的列表。请注意,返回的字典键现在是字符串,表示工作表名称,如以下代码所示:
ts = pd.read_excel(filepath,
engine='openpyxl',
index_col=1,
sheet_name=['2017','2018'],
parse_dates=True)
ts.keys()
>> dict_keys(['2017', '2018'])
- 如果你想从所有可用工作表中读取数据,可以传递
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 行且 DatetimeIndex
从 2017-01-01
到 2018-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
- 当返回多个 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)
- 如果你只读取一个工作表,行为会略有不同。默认情况下,
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()
的更多信息,请参考官方文档:
-
pandas.read_excel
:pandas.pydata.org/docs/reference/api/pandas.read_excel.html
-
pandas.ExcelFile.parse
:pandas.pydata.org/docs/reference/api/pandas.ExcelFile.parse.html
从 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 Python(Boto3),以便从 S3 桶读取文件。此外,您还将学习如何使用storage_options
参数,它在 pandas 中的许多读取函数中可用,用于在没有 Boto3 库的情况下从 S3 读取数据。
要在 pandas 中使用 S3 URL(例如,s3://bucket_name/path-to-file
),您需要安装 s3fs
库。您还需要安装一个 HTML 解析器,当我们使用 read_html()
时。比如,解析引擎(HTML 解析器)可以选择安装 lxml
或 html5lib
;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 文件。让我们从以下步骤开始:
- 要从 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
-
相反,您需要原始内容,这会给您一个如下所示的 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.4 中,请注意值没有用逗号分隔(不是逗号分隔文件);相反,文件使用分号(
;
)来分隔值。
文件中的第一列是 Date
列。您需要解析(使用 parse_date
参数)并将其转换为 DatetimeIndex
(index_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 在我们文件中的示例:
-
一个虚拟托管样式的 URL 或对象 URL:
tscookbook.s3.us-east-1.amazonaws.com/AirQualityUCI.xlsx
-
一个路径样式的 URL:
s3.us-east-1.amazonaws.com/tscookbook/AirQualityUCI.xlsx
-
一个 S3 协议:
s3://tscookbook/AirQualityUCI.csv
在此示例中,您将读取 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
文件:
- 您将从将您的 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']
- AWS 访问密钥 ID 和 密钥访问密钥 现在存储在
AWS_ACCESS_KEY
和AWS_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
- 或者,您可以使用 AWS 的 Python SDK(Boto3)来实现类似的功能。
boto3
Python 库为您提供了更多的控制和额外的功能(不仅仅是读取和写入 S3)。您将传递之前存储在AWS_ACCESS_KEY
和AWS_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 接口。目前,您将使用客户端接口。
- 您将使用
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:
- 在以下示例中,我们将从 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))
>>
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')
- 显示
Total cases
、Total deaths
和Cases 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 方案之一,包括 http
和 https
、ftp
、s3
、gs
,或者 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>
属性的字典,例如id
或class
。例如,你可以使用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
的错误,请安装它,你需要同时安装html5lib
和beautifulSoup4
。
使用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 引擎来处理文件。你可以安装fastparquet或PyArrow,后者是 pandas 的默认选择。
使用conda安装 PyArrow,运行以下命令:
conda install -c conda-forge pyarrow
使用pip安装 PyArrow,运行以下命令:
pip install pyarrow
如何操作…
PyArrow库允许你将额外的参数(**kwargs)
传递给pandas.read_parquet()
函数,从而在读取文件时提供更多的选项,正如你将要探索的那样。
读取所有分区
以下步骤用于一次性读取LA_weather.parquet
文件夹中的所有分区:
- 创建一个路径来引用包含分区的 Parquet 文件夹,并将其传递给
read_parquet
函数。
file = Path('../../datasets/Ch2/LA_weather.parquet/')
df = pd.read_parquet(file,
engine='pyarrow')
- 你可以使用
.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
参数指定列:
- 由于数据按年份进行分区,你可以使用
filters
参数来指定特定的分区。在下面的示例中,你将只读取 2012 年的分区:
filters = [('year', '==', 2012)]
df_2012 = pd.read_parquet(file,
engine='pyarrow',
filters=filters)
- 要读取一组分区,例如 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)
- 另一个有用的参数是
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
还有更多…
-
回想一下在准备工作部分中,您已将 PyArrow 库安装为处理 Parquet 文件的后端引擎。当您在 pandas 中使用
read_parquet()
读取函数时,pyarrow
引擎是默认的。 -
既然您已经安装了该库,您可以直接利用它像使用 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_pa
和df_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,例如 Dask、Polars 和 Modin。
在本教程中,您将学习 pandas 中处理大数据集的技巧,如 分块处理。随后,您将探索三个新库:Dask、Polars 和 Modin。这些库是 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()
。
- 首先,让我们开始使用传统方法,通过
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
- 使用相同的
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
-
要检索每个数据块中的数据,你可以使用
get_chunk()
方法一次检索一个数据块,或者使用循环检索所有数据块,或者简单地使用pandas.concat()
函数: -
选项 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)
- 选项 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])
- 选项 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 文件。
- 首先,导入 Polars 库:
import polars as pl
from pathlib import Path
file_path = Path('../../datasets/Ch2/yellow_tripdata_2023.csv')
- 现在你可以使用
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
- 你可以使用
.head()
方法打印出 Polars DataFrame 的前 5 条记录,这与 pandas 类似。
df_pl.head()
- 要获取 Polars DataFrame 的总行数和列数,你可以使用
.shape
属性,类似于 pandas 的用法:
df_pl.shape
>>
(16186386, 20)
- 如果你决定使用 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 文件。
- 首先,从
dask
库中导入dataframe
模块:
import dask.dataframe as dd
from pathlib import Path
file_path = Path('../../datasets/Ch2/yellow_tripdata_2023.csv')
- 现在你可以使用
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 利用率方面的有趣输出为例。一个人可能会以为什么都没有被读取。让我们运行一些测试来了解发生了什么。
- 你将使用在 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 对象。我们稍后会再次提到它。
- 最后,尝试使用 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 条记录,仅此而已。这样可以节省内存并提高性能。
- 使用 head 方法打印 Dask DataFrame 的前五条记录:
df_dk.head()
你会注意到,前五(5)条记录的打印结果与使用 pandas 时的预期类似。
- 要获取数据集中的总记录数,你可以使用
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
- 最后,如果你能够将 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 库作为Dask或Ray的封装器,或者更具体地说,是其上面的抽象,使用与 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
数据库扩展了你可以存储的内容,包括文本、图像和媒体文件,并且被设计用于在大规模下进行高效的读写操作。数据库能够存储数 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
的容器。username
为 postgres
,密码是 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 数据库时提供了额外的功能和特性。请按照以下步骤操作:
- 开始时导入必要的库。你将从
database.cfg
文件中导入所需的连接参数,正如技术要求部分中所突出显示的那样。你将创建一个 Python 字典来存储所有连接到数据库所需的参数值,比如host
、database
名称、user
名称和password
:
import psycopg
import pandas as pd
import configparser
config = configparser.ConfigParser()
config.read('database.cfg')
params = dict(config['POSTGRESQL'])
- 你可以通过将参数传递给
connect()
方法来建立连接。一旦连接成功,你可以创建一个游标对象,用来执行 SQL 查询:
conn = psycopg.connect(**params)
cursor = conn.cursor()
- 游标对象提供了多个属性和方法,包括
execute
、executemany
、fetchall
、fetchmany
和fetchone
。以下代码使用游标对象传递 SQL 查询,然后使用rowcount
属性检查该查询所产生的记录数:
cursor.execute("""
SELECT date, close, volume
FROM msft
ORDER BY date;
""")
cursor.rowcount
>> 1259
- 执行查询后返回的结果集将不包括标题(没有列名)。或者,你可以通过使用
description
属性从游标对象中获取列名,代码如下所示:
cursor.description
>>
[<Column 'date', type: varchar(50) (oid: 1043)>,
<Column 'close', type: float4 (oid: 700)>,
<Column 'volume', type: int4 (oid: 23)>]
- 你可以使用列表推导式从
cursor.description
中提取列名,并在创建 DataFrame 时将其作为列标题传入:
columns = [col[0] for col in cursor.description]
columns
>>
['date', 'close', 'volume']
- 要获取执行查询所产生的结果,你将使用
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
类型。
- 使用
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
- 关闭游标和数据库连接:
cursor.close()
conn.close()
注意,psycopg
连接和游标可以在 Python 的 with
语句中用于处理异常,以便在提交事务时进行异常处理。游标对象提供了三种不同的获取函数;即 fetchall
、fetchmany
和 fetchone
。fetchone
方法返回一个单独的元组。以下示例展示了这一概念:
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_sql
、pandas.read_sql_query
和 pandas.read_sql_table
。让我们执行以下步骤:
- 开始时导入必要的库。注意,在幕后,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
- 你也可以使用
pandas.read_sql_query
来完成相同的操作:
df = pd.read_sql_query(query,
engine,
index_col='date',
parse_dates={'date':'%Y-%m-%d'})
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_query
和read_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 数据库的psycopg
或pg8000
。 -
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_sql
、read_sql_query
、read_sql_table
和 to_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
参数应该减少内存使用,因为每次加载的行数较少。
另见:
若要获取关于这些主题的更多信息,请查看以下链接:
-
对于SQLAlchemy,你可以访问
www.sqlalchemy.org/
-
关于
pandas.read_sql
函数,请访问pandas.pydata.org/docs/reference/api/pandas.read_sql_table.html
-
关于
pandas.read_sql_query
函数,请访问pandas.pydata.org/docs/reference/api/pandas.read_sql_query.html
-
关于
pandas.read_sql_table
函数,请访问pandas.pydata.org/docs/reference/api/pandas.read_sql_table.html
从 Snowflake 读取数据
一个非常常见的数据分析提取来源通常是公司的数据仓库。数据仓库托管了大量的数据,这些数据大多是集成的,用来支持各种报告和分析需求,此外还包含来自不同源系统的历史数据。
云计算的发展为我们带来了云数据仓库,如Amazon Redshift、Google BigQuery、Azure SQL Data Warehouse和Snowflake。
在这个教程中,你将使用Snowflake,一个强大的软件即服务(SaaS)基于云的数据仓库平台,可以托管在不同的云平台上,例如Amazon Web Services(AWS)、Google Cloud Platform(GCP)和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
- 我们将从导入必要的库开始:
import pandas as pd
from snowflake import connector
from configparser import ConfigParser
- 使用
ConfigParser
,你将提取[SNOWFLAKE]
部分下的内容,以避免暴露或硬编码你的凭据。你可以读取[SNOWFLAKE]
部分的所有内容并将其转换为字典对象,如下所示:
config = ConfigParser()
config.read(database.cfg')
params = dict(config['SNOWFLAKE'])
- 你需要将参数传递给
connector.connect()
来与 Snowflake 建立连接。我们可以轻松地 解包 字典内容,因为字典的键与参数名匹配。一旦连接建立,我们可以创建我们的 游标:
con = connector.connect(**params)
cursor = con.cursor()
- 游标对象有许多方法,如
execute
、fetchall
、fetchmany
、fetchone
、fetch_pandas_all
和fetch_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()
- 使用
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
- 从前面的输出中可以看到,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
- 检查 DataFrame 的索引。打印前两个索引:
df_ts.index[0:2]
>>
DatetimeIndex(['1994-02-21', '1997-04-14'], dtype='datetime64[ns]', name='O_ORDERDATE', freq=None)
- 最后,你可以关闭游标和当前连接。
Cursor.close()
con.close()
现在你拥有一个具有DatetimeIndex
的时间序列 DataFrame。
使用 SQLAlchemy
在之前的例子中,从关系数据库读取数据
,你探索了 pandas 的 read_sql
、read_sql_query
和 read_sql_table
函数。这是通过使用 SQLAlchemy 和安装一个支持的方言来完成的。在这里,我们将在安装 snowflake-sqlalchemy
驱动程序后使用 Snowflake 方言。
SQLAlchemy 与 pandas 更好地集成,正如你将在本节中体验的那样。
- 开始时,导入必要的库,并从
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'])
- 你将使用 URL 类来生成 URL 连接字符串。我们将创建我们的引擎对象,然后使用
engine.connect()
打开连接:
url = URL(**params)
engine = create_engine(url)
connection = engine.connect()
- 现在,你可以使用
read_sql
或read_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
列并将其设置为索引的。
- 最后,关闭数据库连接。
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
。
- 从导入必要的库并从
database.cfg
文件的[SNOWFLAKE]
部分读取 Snowflake 连接参数开始
from snowflake.snowpark import Session
from configparser import ConfigParser
config = ConfigParser()
config.read('database.cfg')
params = dict(config['SNOWFLAKE'])
- 通过与 Snowflake 数据库建立连接来创建会话
session = Session.builder.configs(params).create()
- 会话有多个
DataFrameReader
方法,如read
、table
和sql
。这些方法中的任何一个都会返回一个 Snowpark DataFrame 对象。返回的 Snowpark DataFrame 对象可以使用to_pandas
方法转换为更常见的 pandas DataFrame。你将探索read
、table
和sql
方法,以返回相同的结果集。
从 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()
,每个游标还具有若干属性,包括 description
、rowcount
、rownumber
等。注意,当使用 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_sql
、pandas.read_sql_query
和pandas.read_sql_query
读取函数,并利用许多可用参数在读取时转换和处理数据,如index_col
和parse_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-python
和snowflake-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 时,默认会将名称转换为小写。
另见
-
有关Snowflake Python 连接器的更多信息,您可以访问官方文档:
docs.snowflake.com/en/user-guide/python-connector.html
-
有关Snowflake SQLAlchemy的更多信息,您可以访问官方文档:
docs.snowflake.com/en/user-guide/sqlalchemy.html
-
有关Snowpark API的更多信息,您可以访问官方文档:
docs.snowflake.com/developer-guide/snowpark/reference/python/latest/snowpark/index
从文档数据库读取数据
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-database
。MongoDB 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
。
执行以下步骤:
- 首先,让我们导入必要的库:
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')
更多信息,请阅读这里的文档
- 一旦连接成功,你可以列出所有可用的数据库。在此示例中,我将数据库命名为
stock_data
,集合命名为microsoft
:
client.list_database_names()
>>
['admin', 'config', 'local', 'stock_data']
- 你可以使用
list_collection_names
列出stock_data
数据库下可用的集合:
db = client['stock_data']
db.list_collection_names()
>>
['microsoft', 'system.buckets.microsoft', 'system.views']
- 现在,你可以指定查询哪个集合。在这个例子中,我们感兴趣的是名为
microsoft
的集合:
collection = db['microsoft']
- 现在,使用
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()
,以及其他属性,如address
和HOST
。
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_options
、read_preference
、write_concern
和 read_concern
,其中后两个参数更关注节点间的操作以及如何确定操作是否成功。
同样地,一旦你拥有了 PyMongo
数据库对象,你可以使用不同的语法来指定集合,如以下示例所示:
# Specifying the collection
collection = db.microsoft
collection = db['microsoft']
collection = db.get_collection('microsoft')
get_collection()
方法提供了额外的参数,类似于 get_database()
。
前面的三个语法变体返回一个 pymongo.database.Collection
对象,它带有额外的内建方法和属性,如 find
、find_one
、find_one_and_delete
、find_one_and_replace
、find_one_and_update
、update
、update_one
、update_many
、delete_one
和 delete_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
,只返回date
、close
和volume
字段。
最后,在我们前面的例子中,注意查询中使用的$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 list 或 tuple:
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 数据或传感器数据的大型数据集时提供了更好的性能。过去,时间序列数据库的常见使用场景主要与金融股票数据相关,但它们的应用领域已经扩展到其他学科和领域。在本教程中,您将探索三种流行的时间序列数据库:InfluxDB、TimescaleDB 和 TDEngine。
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 个 子表,命名为 d0
到 d9999
,每个表包含大约 10,000 行和四列(ts
、current
、voltage
和 phase
)。你可能无法在 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 进行读取和写入功能。让我们开始吧:
- 首先,让我们导入必要的库:
from influxdb_client import InfluxDBClient
import pandas as pd
- 要使用
InfluxDBClient(url="http://localhost:8086", token=token)
建立连接,你需要定义token
、org
和bucket
变量:
token = "c5c0JUoz-\
joisPCttI6hy8aLccEyaflyfNj1S_Kff34N_4moiCQacH8BLbLzFu4qWTP8ibSk3JNYtv9zlUwxeA=="
org = "ts"
bucket = "tscookbook"
可以将桶看作是关系数据库中的数据库。
- 现在,你可以通过将
url
、token
和org
参数传递给InlfuxDBClient()
来建立连接:
client = InfluxDBClient(url="http://localhost:8086",
token=token,
org=org)
- 接下来,你将实例化
query_api
:
query_api = client.query_api()
- 传递你的 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)
- 在前面的 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
- 如果你只想检索时间和温度列,你可以更新 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 来实现:
- 导入 SQLAlchemy 和 pandas
import pandas as pd
from sqlalchemy import create_engine
- 使用正确的连接字符串创建 PostgreSQL 后端的引擎对象。
engine =\
create_engine("postgresql+psycopg2://postgres:password@localhost:5432/postgres")
- 最后,使用
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 部分中有所描述。
- 从导入所需的库开始。
import taosrest
import pandas as pd
- 你将创建一个 Python 字典,存储所有连接数据库所需的参数值,如
url
、user
和password
。
import configparser
config = configparser.ConfigParser()
config.read('database.cfg')
params = dict(config['TDENGINE'])
- 建立与服务器的连接。
conn = taosrest.connect(**params)
- 运行以下查询,并使用连接对象 conn 的
query
方法执行该查询。
query = """
SELECT *
FROM test.meters
WHERE location = 'California.LosAngles'
LIMIT 100000;
"""
results = conn.query(query)
- 你可以验证结果集中的行数和列名。
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}]
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"
来填充新列中的相应值。
还有更多…
在使用InfluxDB和influxdb-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。
另见
-
如果你是 Flux 查询语言的新手,可以查看官方文档中的Flux 入门指南:
docs.influxdata.com/influxdb/v2.0/query-data/get-started/
. -
请参考官方的InfluxDB-Client Python 库文档,地址为 GitHub:
github.com/influxdata/influxdb-client-python
. -
要了解更多关于TDEngine Python 库的信息,请参阅官方文档:
docs.tdengine.com/cloud/programming/client-libraries/python/