Python-大数据分析-全-

Python 大数据分析(全)

原文:annas-archive.org/md5/5058e6970bd2a8d818ecc1f7f8fef74a

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章:前言

关于

本节简要介绍了作者、这本书的内容、你入门所需的技术技能,以及完成活动和练习所需的硬件和软件要求。

关于本书

实时处理大数据具有挑战性,因为它涉及到可扩展性、信息不一致性和容错性等问题。《Python 大数据分析》教你如何使用工具来应对数据的爆炸性增长。在本书中,你将学习如何将数据聚合成有用的维度以供后续分析,提取统计量,并将数据集转换成特征以供其他系统使用。

本书以介绍如何使用 pandas 在 Python 中进行数据处理开始。然后,你将熟悉统计分析和绘图技术。通过多种动手实践,你将学会如何使用 Dask 分析分布在多台计算机上的数据。随着进度的推进,你将学习如何在数据无法完全放入内存时,如何聚合数据以用于绘图。你还将探索 Hadoop(HDFS 和 YARN),这将帮助你处理更大的数据集。本书还涵盖了 Spark,并解释了它如何与其他工具互动。

本书结束时,你将能够启动自己的 Python 环境,处理大文件,并操控数据生成统计信息、指标和图表。

关于作者

伊凡·马林是 Daitan Group(总部位于坎皮纳斯的软件公司)的一名系统架构师和数据科学家。他为大规模数据设计大数据系统,并使用 Python 和 Spark 实现端到端的机器学习管道。他还是圣保罗数据科学、机器学习和 Python 的积极组织者,并曾在大学层级开设过 Python 数据科学课程。

安基特·舒克拉是世界技术公司(World Wide Technology,一家领先的美国技术解决方案提供商)的一名数据科学家,他负责开发和部署机器学习与人工智能解决方案,以解决商业问题并为客户创造实际的经济价值。他还参与公司的研发计划,负责生产知识产权,建立新领域的能力,并在企业白皮书中发布前沿研究。除了调试 AI/ML 模型外,他还喜欢阅读,并且是个大食客。

萨朗·VK是 StraitsBridge Advisors 的首席数据科学家,他的职责包括需求收集、解决方案设计、开发以及使用开源技术开发和产品化可扩展的机器学习、人工智能和分析解决方案。与此同时,他还支持售前和能力建设。

学习目标

  • 使用 Python 读取并将数据转换成不同格式

  • 使用磁盘上的数据生成基本统计信息和指标

  • 使用分布在集群上的计算任务

  • 将各种来源的数据转换为存储或查询格式

  • 为统计分析、可视化和机器学习准备数据

  • 以有效的视觉形式呈现数据

方法

《Python 大数据分析》采取实践方法,帮助理解如何使用 Python 和 Spark 处理数据并将其转化为有用的内容。它包含多个活动,使用实际商业场景,让你在高度相关的背景下实践并应用你的新技能。

受众

《Python 大数据分析》是为希望掌握数据控制与转化成有价值洞察方法的 Python 开发者、数据分析师和数据科学家设计的。对统计测量和关系型数据库的基本知识将帮助你理解本书中解释的各种概念。

最低硬件要求

为了获得最佳学生体验,我们建议以下硬件配置:

处理器:Intel 或 AMD 4 核或更高

内存:8 GB RAM

存储:20 GB 可用空间

软件要求

你需要提前安装以下软件。

以下任一操作系统:

  • Windows 7 SP1 32/64 位

  • Windows 8.1 32/64 位 或 Windows 10 32/64 位

  • Ubuntu 14.04 或更高版本

  • macOS Sierra 或更高版本

  • 浏览器:Google Chrome 或 Mozilla Firefox

  • Jupyter lab

你还需要提前安装以下软件:

  • Python 3.5+

  • Anaconda 4.3+

以下 Python 库已包含在 Anaconda 安装中:

  • matplotlib 2.1.0+

  • iPython 6.1.0+

  • requests 2.18.4+

  • NumPy 1.13.1+

  • pandas 0.20.3+

  • scikit-learn 0.19.0+

  • seaborn 0.8.0+

  • bokeh 0.12.10+

这些 Python 库需要手动安装:

  • mlxtend

  • version_information

  • ipython-sql

  • pdir2

  • graphviz

约定

文本中的代码字、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账户名如下所示:“要将数据转换为正确的数据类型,我们可以使用转换函数,例如 to_datetimeto_numericastype。”

一段代码如下所示:

before the sort function:[23, 66, 12, 54, 98, 3]
after the sort function: [3, 12, 23, 54, 66, 98]

新术语和重要词汇用粗体显示。屏幕上看到的词汇,例如在菜单或对话框中,按如下方式出现在文本中:“Pandas (pandas.pydata.org) 是一个广泛应用于数据科学社区的数据处理和分析库。”

安装与设置

安装 Anaconda:

  1. 访问 www.anaconda.com/download/ 在浏览器中。

  2. 根据你使用的操作系统点击 Windows、Mac 或 Linux。

  3. 接下来,点击下载选项,确保下载最新版本。

  4. 下载后打开安装程序。

  5. 按照安装程序中的步骤操作,就完成了!你的 Anaconda 分发版已经准备好。

PySpark 可以在 PyPi 上获得。要安装 PySpark,请运行以下命令:

pip install pyspark --upgrade

更新 Jupyter 并安装依赖项:

  1. 搜索 Anaconda Prompt 并打开它。

  2. 输入以下命令来更新 Conda 和 Jupyter:

    #Update conda
    conda update conda
    #Update Jupyter
    conda update Jupyter
    #install packages
    conda install numpy
    conda install pandas
    conda install statsmodels
    conda install matplotlib
    conda install seaborn
    
  3. 从 Anaconda Prompt 打开 Jupyter Notebook,请使用以下命令:

    jupyter notebook
    pip install -U scikit-learn
    

安装代码包

将课堂代码包复制到 C:/Code 文件夹中。

附加资源

本书的代码包也托管在 GitHub 上,网址为:github.com/TrainingByPackt/Big-Data-Analysis-with-Python

我们还提供了其他代码包,来自我们丰富的图书和视频目录,网址为:github.com/PacktPublishing/。赶快去看看吧!

第二章:第一章

Python 数据科学栈

学习目标

我们将通过理解 Python 在数据操作和可视化中的强大能力,开启我们的学习之旅,进行有用的分析。

到本章结束时,你将能够:

  • 使用 Python 数据科学栈的所有组件

  • 使用 pandas DataFrame 操作数据

  • 使用 pandas 和 Matplotlib 创建简单的图表

在本章中,我们将学习如何使用 NumPy、Pandas、Matplotlib、IPython、Jupyter notebook。稍后我们将探讨virtualenvpyenv的部署方式,紧接着我们将使用 Matplotlib 和 Seaborn 库绘制基本的可视化图表。

引言

Python 数据科学栈是一个非正式的名称,用来描述一组协同工作的库,用于解决数据科学问题。关于哪些库应当列入此名单,并没有统一的标准;通常这取决于数据科学家和待解决的问题。我们将介绍最常用的库,并解释如何使用它们。

在本章中,我们将学习如何使用 Python 数据科学栈来操作表格数据。Python 数据科学栈是处理大规模数据集的第一步,尽管这些库本身通常不用于大数据处理。这里使用的思想和方法将对我们后续处理大数据集时非常有帮助。

Python 库和包

Python 之所以是一门强大的编程语言,其中一个重要原因就是它所附带的库和包。Python 包索引PyPI)中有超过 130,000 个包,且这个数字还在增长!让我们一起来探索一些数据科学栈中的库和包。

数据科学栈的组件如下:

  • NumPy:一个数值计算库

  • pandas:一个数据操作和分析库

  • SciPy 库:建立在 NumPy 基础上的一组数学算法

  • Matplotlib:一个绘图和图表库

  • IPython:一个交互式 Python shell

  • Jupyter notebook:用于交互式计算的 Web 文档应用

这些库的组合构成了一个强大的工具集,用于处理数据操作和分析。我们将逐一介绍每个库,探索它们的功能,并展示它们是如何协同工作的。让我们从解释器开始。

IPython:一个强大的交互式 shell

IPython shell(ipython.org/)是一个交互式 Python 命令解释器,支持多种语言。它允许我们快速测试想法,而不需要创建文件并运行它们。大多数 Python 安装包中都会包含一个命令解释器,通常被称为shell,你可以在其中逐行执行命令。尽管很方便,但标准的 Python shell 使用起来有些繁琐。IPython 提供了更多的功能:

  • 会话之间的输入历史记录功能,确保你在重启 shell 后,之前输入的命令可以被重新使用。

  • 使用Tab自动补全命令和变量,你可以输入 Python 命令、函数或变量的首字母,IPython 会自动补全它。

  • 魔法命令扩展了 shell 的功能。魔法函数可以增强 IPython 的功能,例如添加一个模块,可以在模块在磁盘上被修改后重新加载,而不需要重启 IPython。

  • 语法高亮。

练习 1:使用 IPython 命令与 Python Shell 交互

启动 Python shell 很简单。让我们按照以下步骤与 IPython shell 互动:

  1. 要启动 Python shell,请在控制台中输入ipython命令:

    > ipython
    In [1]:
    

    IPython shell 现在已准备好,等待进一步的命令。首先,让我们做一个简单的练习,解决一个排序问题,使用一种基本的排序方法,称为直接插入法

  2. 在 IPython shell 中,复制并粘贴以下代码:

    import numpy as np
    vec = np.random.randint(0, 100, size=5)
    print(vec)
    

    现在,随机生成的数字的输出将类似于以下内容:

    [23, 66, 12, 54, 98, 3]
    
  3. 使用以下逻辑按升序打印vec数组的元素:

    for j in np.arange(1, vec.size):
        v = vec[j]
        i = j
        while i > 0 and vec[i-1] > v:
            vec[i] = vec[i-1]
            i = i - 1
        vec[i] = v
    

    使用print(vec)命令在控制台打印输出:

    [3, 12, 23, 54, 66, 98]
    
  4. 现在修改代码。将参数从创建一个包含 5 个元素的数组更改为创建一个包含 20 个元素的数组,使用箭头编辑粘贴的代码。修改相关部分后,使用箭头移动到代码的末尾并按Enter键执行。

注意左侧的数字,表示指令编号。这个数字会一直增加。我们将值赋给一个变量并对该变量执行操作,得到交互式结果。我们将在接下来的章节中使用 IPython。

Jupyter Notebook

Jupyter notebook (jupyter.org/) 最初作为 IPython 的一部分,但在版本 4 中被分离并扩展,现在作为一个独立的项目存在。Notebook 概念基于扩展交互式 shell 模型,创建可以运行代码、显示文档并展示结果(如图表和图像)的文档。

Jupyter 是一个 Web 应用程序,因此它直接在 Web 浏览器中运行,无需安装单独的软件,且可以在互联网上使用。Jupyter 可以使用 IPython 作为运行 Python 的内核,但它支持超过 40 个由开发者社区贡献的内核。

注意

在 Jupyter 的术语中,内核是执行代码单元中的代码的计算引擎。例如,IPython 内核执行 notebook 中的 Python 代码。也有其他语言的内核,如 R 和 Julia。

它已经成为一个事实上的平台,从初学者到高级用户,从小型到大型企业,甚至是学术界,都可以用它来执行与数据科学相关的操作。在过去的几年里,它的受欢迎程度大幅度提高。一个 Jupyter notebook 包含你在其上运行的代码的输入和输出。它支持文本、图像、数学公式等,是一个开发代码和交流结果的绝佳平台。由于它的 Web 格式,notebook 可以通过互联网共享。它还支持 Markdown 标记语言,并将 Markdown 文本渲染为富文本,支持格式化及其他特性。

正如我们之前所见,每个 notebook 都有一个内核。这个内核是执行单元格中代码的解释器。一个 notebook 的基本单位叫做单元格。单元格是一个容器,可以容纳代码或文本。我们有两种主要类型的单元格:

  • 代码单元格

  • Markdown 单元格

代码单元格接受要在内核中执行的代码,并在其下方显示输出。Markdown 单元格接受 Markdown,当单元格执行时,会将文本解析为格式化文本。

让我们运行以下练习,获得在 Jupyter notebook 中的实际操作经验。

一个 notebook 的基本组件是单元格,单元格根据选择的模式可以接受代码或文本。

让我们启动一个 notebook,演示如何使用单元格,单元格有两种状态:

  • 编辑模式

  • 运行模式

在编辑模式下,单元格的内容可以编辑,而在运行模式下,单元格准备好执行,可以由内核执行或被解析为格式化文本。

你可以通过使用插入菜单选项或使用键盘快捷键Ctrl + B来添加新单元格。单元格可以通过菜单或快捷键Y(代码单元格)和M(Markdown 单元格)在 Markdown 模式和代码模式之间转换。

要执行一个单元格,点击运行选项或使用Ctrl + Enter快捷键。

练习 2:开始使用 Jupyter Notebook

让我们执行以下步骤,演示如何开始在 Jupyter notebook 中执行简单的程序。

第一次使用 Jupyter notebook 可能会有些困惑,但让我们尝试探索它的界面和功能。这个练习的参考 notebook 可以在 GitHub 上找到。

现在,启动一个 Jupyter notebook 服务器并按照以下步骤进行操作:

  1. 要启动 Jupyter notebook 服务器,在控制台运行以下命令:

    > jupyter notebook
    
  2. 成功运行或安装 Jupyter 后,打开浏览器窗口并访问localhost:8888来访问 notebook。

  3. 你应该能看到一个类似于以下截图的 notebook:图 1.1:Jupyter notebook

    图 1.1:Jupyter notebook
  4. 接下来,在右上角点击新建,从列表中选择Python 3

  5. 一个新的 notebook 应该会出现。首先出现的输入单元格是 代码 单元格。默认单元格类型是 代码。你可以通过 单元格 菜单下的 单元格类型 选项来更改它:图 1.2:Jupyter 单元格菜单中的选项

    图 1.2:Jupyter 单元格菜单中的选项
  6. 现在,在新生成的 代码 单元格中,在第一个单元格中添加以下算术函数:

    In []: x = 2
           print(x*2)
    Out []: 4
    
  7. 现在,添加一个返回两个数字算术平均值的函数,然后执行该单元格:

    In []: def mean(a,b):
           return (a+b)/2
    
  8. 现在,使用 mean 函数,并用两个值 10 和 20 调用该函数。执行此单元格。会发生什么?函数被调用,答案会被打印出来:

    In []: mean(10,20)
    Out[]: 15.0
    
  9. 我们需要记录这个函数。现在,创建一个新的 Markdown 单元格,并编辑单元格中的文本,记录该函数的功能:图 1.3:Jupyter 中的 Markdown

    图 1.3:Jupyter 中的 Markdown
  10. 然后,插入一个来自网络的图片。其目的是 notebook 作为一个文档,应该记录分析的所有部分,因此有时我们需要从其他来源插入一个图表或图形来解释某个观点。

  11. 现在,最后在同一个 Markdown 单元格中插入 LaTex 数学表达式:

图 1.4:Jupyter Markdown 中的 LaTex 表达式

图 1.4:Jupyter Markdown 中的 LaTex 表达式

正如我们将在本书其余部分看到的,notebook 是我们分析过程的基石。我们刚才遵循的步骤展示了不同类型单元格的使用及我们记录分析过程的不同方式。

IPython 或 Jupyter?

IPython 和 Jupyter 在分析工作流中都有其作用。通常,IPython shell 用于快速交互和更为数据密集的工作,如调试脚本或运行异步任务。而 Jupyter notebook 则非常适合用来展示结果,并通过代码、文本和图形生成可视化叙事。我们将展示的大多数示例都可以在这两者中执行,除了图形部分。

IPython 能显示图形,但通常图形的加入在 notebook 中更为自然。在本书中,我们通常使用 Jupyter notebook,但这些指令同样适用于 IPython notebook。

活动 1:IPython 和 Jupyter

让我们展示在 IPython 和 Jupyter 中常见的 Python 开发。我们将导入 NumPy,定义一个函数,并迭代结果:

  1. 打开 python_script_student.py 文件,在文本编辑器中复制其内容,粘贴到 IPython 的 notebook 中,并执行操作。

  2. 将 Python 脚本中的代码复制并粘贴到 Jupyter notebook 中。

  3. 现在,更新 xc 常量的值。然后,修改函数的定义。

    注意

    该活动的解决方案可以在第 200 页找到。

现在我们已经知道如何在 notebook 中处理函数并动态更改函数定义。当我们在探索和发现某段代码或分析的正确方法时,这非常有帮助。Notebook 支持的迭代方法在原型设计中非常高效,并且比写代码到脚本中再执行、检查结果并重新修改脚本要更快。

NumPy

NumPy (www.numpy.org) 是一个来自 Python 科学计算社区的包。NumPy 非常适合操作多维数组,并对这些数组应用线性代数函数。它还具备与 C、C++ 和 Fortran 代码集成的工具,进一步提高了性能。许多使用 NumPy 作为数值引擎的 Python 包,包括 pandas 和 scikit-learn,都是 SciPy 生态系统的一部分,专门用于数学、科学和工程领域。

要导入这个包,请打开之前活动中使用的 Jupyter notebook,并输入以下命令:

import numpy as np

基本的 NumPy 对象是 ndarray,它是一个同质的多维数组,通常由数字组成,但也可以存储通用数据。NumPy 还包含多个用于数组操作、线性代数、矩阵运算、统计学和其他领域的函数。NumPy 的一个亮点是在科学计算中,矩阵和线性代数操作非常常见。NumPy 的另一个优势是它可以与 C++ 和 FORTRAN 代码集成。NumPy 也被其他 Python 库广泛使用,如 pandas。

SciPy

SciPy (www.scipy.org) 是一个数学、科学和工程学的库生态系统。NumPy、SciPy、scikit-learn 等都是这个生态系统的一部分。它也是一个库的名称,包含了许多科学领域的核心功能。

Matplotlib

Matplotlib (matplotlib.org) 是一个用于 Python 的二维图形绘制库。它能够生成多种硬拷贝格式的图形,供互动使用。它可以使用原生 Python 数据类型、NumPy 数组和 pandas DataFrame 作为数据源。Matplotlib 支持多个后端——支持互动或文件格式输出的部分。这使得 Matplotlib 可以跨平台运行。这种灵活性还允许 Matplotlib 扩展工具包,用于生成其他类型的图形,例如地理图和 3D 图形。

Matplotlib 的交互式界面灵感来自 MATLAB 的绘图界面。可以通过 matplotlib.pyplot 模块访问。文件输出可以直接写入磁盘。Matplotlib 可以在脚本、IPython 或 Jupyter 环境中使用,也可以在 Web 服务器和其他平台中使用。Matplotlib 有时被认为是低级的,因为生成包含更多细节的图表需要多行代码。在本书中,我们将介绍一个常用于分析中的绘图工具——Seaborn 库,它是我们之前提到的扩展之一。

要导入交互式界面,请在 Jupyter notebook 中使用以下命令:

import matplotlib.pyplot as plt

要访问绘图功能,我们将在下一章中更详细地展示如何使用 Matplotlib。

Pandas

Pandas (pandas.pydata.org) 是一个广泛用于数据科学社区的数据操作和分析库。Pandas 旨在处理类似 SQL 表格和 Excel 文件的表格型或标签型数据。

我们将更详细地探讨 pandas 提供的各种操作。现在,了解两个基本的 pandas 数据结构非常重要:series,一种一维数据结构;以及数据科学的工作马——二维数据结构 DataFrame,它支持索引。

DataFrame 和 series 中的数据可以是有序的或无序的,既可以是同质的,也可以是异质的。pandas 的其他优秀功能包括轻松添加或删除行和列,以及 SQL 用户更熟悉的操作,如 GroupBy、连接、子集提取和索引列。Pandas 在处理时间序列数据方面也非常强大,具有易用且灵活的日期时间索引和选择功能。

让我们使用以下命令在之前的 Jupyter notebook 中导入 pandas:

import pandas as pd

使用 Pandas

我们将演示如何使用 pandas 进行数据操作。这个方法被作为其他数据操作工具(如 Spark)的标准,因此学习如何使用 pandas 操作数据是很有帮助的。在大数据管道中,常常将部分数据或数据样本转换为 pandas DataFrame,以应用更复杂的转换、可视化数据,或使用更精细的机器学习模型(例如 scikit-learn 库)。Pandas 在内存中进行单机操作时也非常快速。尽管数据大小与 pandas DataFrame 之间存在内存开销,但它仍然可以快速操作大量数据。

我们将学习如何应用基本操作:

  • 将数据读取到 DataFrame 中

  • 选择和过滤

  • 将函数应用于数据

  • GroupBy 和聚合

  • 可视化来自 DataFrame 的数据

让我们从将数据读取到 pandas DataFrame 开始。

读取数据

Pandas 支持多种数据格式和数据导入方式。我们从更常见的方式开始,读取一个 CSV 文件。Pandas 有一个名为 read_csv 的函数,可以用来读取 CSV 文件,无论是本地文件还是来自 URL 的文件。我们将从美国环保局(EPA)的 Socrata 开放数据计划中读取一些数据,这些数据列出了 EPA 收集的放射性物质含量。

练习 3:使用 Pandas 读取数据

一个分析师如何在没有数据的情况下开始数据分析?我们需要学习如何将数据从互联网源导入到我们的笔记本中,才能开始分析。让我们展示如何使用 pandas 从互联网源读取 CSV 数据,以便我们进行分析:

  1. 导入 pandas 库。

    import pandas as pd
    
  2. 读取汽车里程数据集,可通过以下 URL 获取:github.com/TrainingByPackt/Big-Data-Analysis-with-Python/blob/master/Lesson01/imports-85.data。将其转换为 CSV 格式。

  3. 使用列名为数据命名,通过 read_csv 函数中的 names 参数。

    Sample code : df = pd.read_csv("/path/to/imports-85.csv", names = columns)
    
  4. 使用 pandas 的 read_csv 函数并通过调用 DataFrame 的 head 方法显示前几行:

    import pandas as pd
    df = pd.read_csv("imports-85.csv")
    df.head()
    

    输出如下:

图 1.5:汽车里程数据集的条目

图 1.5:汽车里程数据集的条目

Pandas 可以读取更多格式:

  • JSON

  • Excel

  • HTML

  • HDF5

  • Parquet(使用 PyArrow)

  • SQL 数据库

  • Google Big Query

尝试从 pandas 中读取其他格式,如 Excel 表格。

数据处理

数据处理是指对数据进行任何选择、转换或聚合操作。数据处理可以出于多种原因进行:

  • 选择一个数据子集进行分析

  • 清洗数据集,移除无效、错误或缺失的值

  • 将数据分组为有意义的集合并应用聚合函数

Pandas 的设计旨在让分析师以高效的方式进行这些转换。

选择与过滤

Pandas DataFrame 可以像 Python 列表一样进行切片。例如,要选择 DataFrame 的前 10 行子集,可以使用 [0:10] 语法。在以下截图中,我们看到选择 [1:3] 区间,NumPy 表示法选择了行 12

图 1.6:Pandas DataFrame 中的选择

在接下来的章节中,我们将深入探讨选择与过滤操作。

使用切片选择行

在进行数据分析时,我们通常希望查看数据在特定条件下的不同表现,例如比较几列数据、选择仅有几列帮助阅读数据,或进行绘图。我们可能想查看特定值,例如当一列具有特定值时,其他数据的表现如何。

在使用切片选择之后,我们可以使用其他方法,例如 head 方法,从数据框的开头只选择几行。但如何选择数据框中的某些列呢?

要选择一列,只需使用列名。我们将使用 notebook。使用以下命令选择数据框中的 cylinders 列:

df['State']

输出如下:

图 1.7:显示状态的数据显示框

图 1.7:显示状态的数据显示框

另一种选择方式是通过列中的特定值进行筛选。例如,假设我们想选择所有State列中值为MN的行。我们该如何做呢?尝试使用 Python 的相等运算符和数据框选择操作:

df[df.State == "MN"]

图 1.8:显示 MN 状态的数据显示框

图 1.8:显示 MN 状态的数据显示框

可以同时应用多个筛选器。当组合多个筛选器时,可以使用 ORNOTAND 逻辑运算符。例如,要选择所有 State 等于 AKLocationNome 的行,请使用 & 运算符:

df[(df.State == "AK") & (df.Location == "Nome")]

图 1.9:显示状态为 AK 和位置为 Nome 的数据框

图 1.9:显示状态为 AK 和位置为 Nome 的数据框

另一个强大的方法是 .loc。该方法有两个参数,一个是行选择,另一个是列选择,能够实现精细的选择。此时一个重要的注意事项是,根据所应用的操作,返回的类型可以是数据框或系列。当只选择一列时,.loc 方法返回一个系列。这是预期的,因为每个数据框列本身就是一个系列。当需要选择多个列时,也需要特别注意。为此,可以使用两个括号而不是一个,选择你想要的多个列。

练习 4:数据选择和 .loc 方法

正如我们之前看到的,选择数据、分离变量并查看感兴趣的列和行是分析过程的基础。假设我们想分析I-131明尼苏达州的辐射:

  1. 在 Jupyter notebook 中使用以下命令导入 NumPy 和 pandas 库:

    import numpy as np
    import pandas as pd
    
  2. 从 EPA 获取 RadNet 数据集,该数据集可以从 Socrata 项目的github.com/TrainingByPackt/Big-Data-Analysis-with-Python/blob/master/Lesson01/RadNet_Laboratory_Analysis.csv下载:

    url = "https://opendata.socrata.com/api/views/cf4r-dfwe/rows.csv?accessType=DOWNLOAD"
    df = pd.read_csv(url)
    
  3. 首先,使用 ['<列名>'] 符号选择一列。选择 State 列:

    df['State'].head()
    

    输出如下:

    图 1.10:状态列中的数据

    图 1.10:状态列中的数据
  4. 现在,使用 MN 列名筛选所选列中的值:

    df[df.State == "MN"]
    

    输出如下:

    图 1.11:显示包含 MN 状态的数据显示框

    图 1.11:显示包含 MN 状态的数据显示框
  5. 根据条件选择多个列。为过滤添加Sample Type列:

    df[(df.State == 'CA') & (df['Sample Type'] == 'Drinking Water')]
    

    输出如下:

    图 1.12:具有加利福尼亚州和样本类型为饮用水的 DataFrame
  6. 接下来,选择MN州和同位素I-131

    df[(df.State == "MN") ]["I-131"]
    

    输出如下:

    图 1.13:显示具有明尼苏达州和同位素 I-131 的 DataFrame 数据

    图 1.13:显示具有明尼苏达州和同位素 I-131 的 DataFrame 数据

    明尼苏达州(ID 为555)的辐射值最高。

  7. 我们可以更轻松地使用.loc方法,通过州进行过滤,并在同一.loc调用中选择一列:

    df_rad.loc[df_rad.State == "MN", "I-131"]
    df[['I-132']].head()
    

    输出如下:

图 1.14:包含 I-132 的 DataFrame

图 1.14:包含 I-132 的 DataFrame

在这个练习中,我们学习了如何使用 NumPy 切片表示法或.loc方法来过滤和选择值,无论是在列还是行上。这可以帮助我们在分析数据时,仅检查和操作数据的一个子集,而不必一次性处理整个数据集。

注意

.loc筛选的结果是一个.loc对象。因为 DataFrame 可以被理解为一个二维的系列组合,选择一列将返回一个系列。为了使选择仍然返回一个 DataFrame,请使用双括号:

df[['I-132']].head()

对列应用函数

数据永远不会是干净的。在数据集可以被分析之前,总是需要做一些清理工作。数据清理中最常见的任务之一是对列应用一个函数,将值更改为更合适的值。在我们的示例数据集中,当没有测量浓度时,会插入non-detect值。由于这一列是数字型的,分析它可能会变得复杂。我们可以对该列应用转换,将non-detect更改为numpy.NaN,这使得操作数值更为简单,之后可以填充其他值,比如均值等。

要对多个列应用函数,请使用applymap方法,其逻辑与apply方法相同。例如,另一个常见的操作是去除字符串中的空格。我们可以使用applyapplymap函数来修复数据。我们还可以将函数应用于行而不是列,使用 axis 参数(0表示行,1表示列)。

活动 2:处理数据问题

在开始分析之前,我们需要检查数据问题,当我们发现问题时(这非常常见!),我们必须通过转换 DataFrame 来纠正问题。例如,可以通过对某一列或整个 DataFrame 应用函数来解决问题。通常,DataFrame 中的一些数字在读取时未能正确转换为浮点数。我们可以通过应用函数来修复这个问题:

  1. 导入pandasnumpy库。

  2. 从美国环保局读取 RadNet 数据集。

  3. 创建一个包含 RadNet 数据集中放射性核素的数字列的列表。

  4. 对单列使用apply方法,配合 lambda 函数比较Non-detect字符串。

  5. 将某一列中的文本值替换为NaN,使用np.nan

  6. 使用相同的 lambda 比较方法,并在多个列上同时使用applymap方法,使用第一步中创建的列表。

  7. 创建一个包含所有非数字列的列表。

  8. 删除这些列中的空格。

  9. 使用选择和过滤方法,验证字符串列的列名中是否还存在空格。

    注意

    该活动的解决方案可以在第 200 页找到。

列名中的空格可能是多余的,且会使选择和过滤变得更加复杂。修正数字类型对于需要使用数据计算统计值时非常有帮助。如果某个数字列中的值不合法,例如一个数字列中有字符串,统计操作将无法执行。例如,在数据输入过程中,操作员手动输入信息时可能会出错,或者存储文件从一种格式转换为另一种格式时,导致列中留下了不正确的值。

数据类型转换

数据清理中的另一个常见操作是确保数据类型正确。这有助于检测无效值并应用正确的操作。pandas 中主要存储的类型如下:

  • floatfloat64float32

  • integerint64int32

  • datetimedatetime64[ns, tz]

  • timedeltatimedelta[ns]

  • bool

  • object

  • category

数据类型可以通过 pandas 进行设置、读取或推断。通常,如果 pandas 无法检测到列的数据类型,它会假设该列是存储为字符串的对象类型。

为了将数据转换为正确的数据类型,我们可以使用转换函数,如to_datetimeto_numericastype。类别类型,即只能选择有限选项的列,会被编码为category类型。

练习 5:探索数据类型

使用 pandas 的astype函数将我们示例数据框中的数据类型转换为正确的类型。我们将使用来自opendata.socrata.com/的示例数据集:

  1. 按照此处所示导入所需的库:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  2. 按如下方式读取数据集中的数据:

    url = "https://opendata.socrata.com/api/views/cf4r-dfwe/rows.csv?accessType=DOWNLOAD"
    df = pd.read_csv(url)
    
  3. 使用dtypes函数检查 DataFrame 的当前数据类型:

    df.dtypes
    
  4. 使用to_datetime方法将日期从字符串格式转换为datetime格式:

    df['Date Posted'] = pd.to_datetime(df['Date Posted'])
    df['Date Collected'] = pd.to_datetime(df['Date Collected'])
    columns = df.columns
    id_cols = ['State', 'Location', "Date Posted", 'Date Collected', 'Sample Type', 'Unit']
    columns = list(set(columns) - set(id_cols))
    columns
    

    输出结果如下:

    ['Co-60',
     'Cs-136',
     'I-131',
     'Te-129',
     'Ba-140',
     'Cs-137',
     'Cs-134',
     'I-133',
     'I-132',
     'Te-132',
     'Te-129m']
    
  5. 使用 Lambda 函数:

    df['Cs-134'] = df['Cs-134'].apply(lambda x: np.nan if x == "Non-detect" else x)
    df.loc[:, columns] = df.loc[:, columns].applymap(lambda x: np.nan if x == 'Non-detect' else x)
    df.loc[:, columns] = df.loc[:, columns].applymap(lambda x: np.nan if x == 'ND' else x)
    
  6. 使用to_numeric方法对前一个活动中创建的数字列列表应用,转换列为正确的数字类型:

    for col in columns:
        df[col] = pd.to_numeric(df[col])
    
  7. 再次检查列的数据类型。数字列应该是float64类型,日期列应该是datetime64[ns]类型:

    df.dypes
    
  8. 使用astype方法将非数字列转换为category类型:

    df['State'] = df['State'].astype('category')
    df['Location'] = df['Location'].astype('category')
    df['Unit'] = df['Unit'].astype('category')
    df['Sample Type'] = df['Sample Type'].astype('category')
    
  9. 最后一次使用dtype函数检查数据类型:

    df.dtypes
    

    输出结果如下:

图 1.15:DataFrame 及其类型

图 1.15:DataFrame 及其类型

现在我们的数据集看起来不错,所有值都已正确转换为正确的数据类型。但数据修正只是其中的一部分。作为分析师,我们希望从不同的角度理解数据。例如,我们可能想知道哪个州的污染最严重,或者哪种放射性核素在各城市中最不常见。我们可能会问数据集中有效测量的数量。所有这些问题都有一个共同点,那就是涉及对数据进行分组并聚合多个值的转换。使用 pandas,我们可以通过 GroupBy 来实现这一点。让我们看看如何按键进行分组并聚合数据。

聚合与分组

在获得数据集后,分析师可能需要回答一些问题。例如,我们知道每个城市的放射性核素浓度,但分析师可能会被要求回答:平均而言,哪个州的放射性核素浓度最高?

为了回答提出的问题,我们需要以某种方式对数据进行分组,并对其进行聚合计算。但在进入数据分组之前,我们需要准备数据集,以便能够以高效的方式进行操作。在 pandas 的 DataFrame 中获得正确的数据类型可以极大地提高性能,并有助于执行数据一致性检查—它确保数值数据确实是数值数据,并允许我们执行所需的操作来获得答案。

GroupBy 允许我们从更一般的角度查看特征,通过给定 GroupBy 键和聚合操作来排列数据。在 pandas 中,这个操作是通过 GroupBy 方法完成的,操作的列可以是例如 State。请注意 GroupBy 方法后的聚合操作。以下是一些可以应用的操作示例:

  • mean

  • median

  • std (标准差)

  • mad (均值绝对偏差)

  • sum

  • count

  • abs

    一些统计量,例如 均值标准差,只有在数值数据的情况下才有意义。

应用 GroupBy 后,可以选择特定的列并对其应用聚合操作,或者可以对所有剩余的列使用相同的聚合函数进行聚合。像 SQL 一样,GroupBy 可以同时应用于多列,并且可以对选定的列应用多个聚合操作,每列一个操作。

Pandas 中的 GroupBy 命令有一些选项,例如 as_index,可以覆盖将分组键的列转换为索引并将其保留为普通列的标准。当在 GroupBy 操作后将创建新索引时,这很有帮助,例如。

聚合操作可以在多个列上同时进行,并且使用 agg 方法可以同时应用不同的统计方法,通过传递一个字典,字典的键是列名,值是统计操作的列表。

练习 6:聚合和分组数据

记住,我们需要回答哪个州的放射性核素浓度平均值最高。由于每个州有多个城市,我们需要将一个州内所有城市的值合并并计算平均值。这是 GroupBy 的一个应用:按分组计算一个变量的平均值。我们可以使用 GroupBy 来回答这个问题:

  1. 导入所需的库:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  2. opendata.socrata.com/ 加载数据集:

    df = pd.read_csv('RadNet_Laboratory_Analysis.csv')
    
  3. 使用 State 列对 DataFrame 进行分组。

    df.groupby('State')
    
  4. 选择放射性核素 Cs-134 并计算每组的平均值:

    df.groupby('State')['Cs-134'].head()
    
  5. 对所有列执行相同的操作,按州分组并直接应用 mean 函数:

    df.groupby('State').mean().head()
    
  6. 现在,根据多个列进行分组,使用一个包含多个分组列的列表。

  7. 使用 agg 方法对每一列执行多个聚合操作。使用 StateLocation 列:

    df.groupby(['State', 'Location']).agg({'Cs-134':['mean', 'std'], 'Te-129':['min', 'max']})
    

NumPy 与 Pandas

NumPy 函数可以直接应用于 DataFrame,也可以通过 applyapplymap 方法应用。其他 NumPy 函数,如 np.where,也可以与 DataFrame 一起使用。

从 Pandas 导出数据

在 pandas 中创建了中间数据集或最终数据集之后,我们可以将 DataFrame 中的值导出到其他格式。最常用的格式是 CSV,执行此操作的命令是df.to_csv('filename.csv')。其他格式,如 Parquet 和 JSON,也受到支持。

注意

Parquet 格式特别有趣,它是我们将在本书后面讨论的一个大数据格式之一。

练习 7:导出不同格式的数据

完成分析后,我们可能想保存我们的转换数据集并包含所有的修正,这样如果我们想分享这个数据集或重新进行分析,就不必再次转换数据集。我们还可以将分析作为更大数据管道的一部分,甚至将准备好的数据用作机器学习算法的输入。我们可以通过将 DataFrame 导出到正确格式的文件来实现数据导出:

  1. 导入所有必要的库并使用以下命令从数据集中读取数据:

    import numpy as np
    import pandas as pd
    url = "https://opendata.socrata.com/api/views/cf4r-dfwe/rows.csv?accessType=DOWNLOAD"
    df = pd.read_csv(url)
    

    对 RadNet 数据中的数据类型(日期、数值、类别)重新进行所有调整。类型应与 练习 6:聚合和分组数据 中的相同。

  2. 选择数值型列和类别型列,为每个列创建一个列表:

    columns = df.columns
    id_cols = ['State', 'Location', "Date Posted", 'Date Collected', 'Sample Type', 'Unit']
    columns = list(set(columns) - set(id_cols))
    columns
    

    输出如下:

    图 1.16:列列表

    图 1.16:列列表
  3. 应用替换 Non-detectnp.nan 的 lambda 函数:

    df['Cs-134'] = df['Cs-134'].apply(lambda x: np.nan if x == "Non-detect" else x)
    df.loc[:, columns] = df.loc[:, columns].applymap(lambda x: np.nan if x == 'Non-detect' else x)
    df.loc[:, columns] = df.loc[:, columns].applymap(lambda x: np.nan if x == 'ND' else x)
    
  4. 删除类别列中的空格:

    df.loc[:, ['State', 'Location', 'Sample Type', 'Unit']] = df.loc[:, ['State', 'Location', 'Sample Type', 'Unit']].applymap(lambda x: x.strip())
    
  5. 将日期列转换为 datetime 格式:

    df['Date Posted'] = pd.to_datetime(df['Date Posted'])
    df['Date Collected'] = pd.to_datetime(df['Date Collected'])
    
  6. 使用 to_numeric 方法将所有数值型列转换为正确的数值格式:

    for col in columns:
        df[col] = pd.to_numeric(df[col])
    
  7. 将所有类别变量转换为category类型:

    df['State'] = df['State'].astype('category')
    df['Location'] = df['Location'].astype('category')
    df['Unit'] = df['Unit'].astype('category')
    df['Sample Type'] = df['Sample Type'].astype('category')
    
  8. 使用to_csv函数将我们转换后的 DataFrame 导出为 CSV 格式,确保包含正确的值和列。通过index=False排除索引,使用分号作为分隔符sep=";",并将数据编码为 UTF-8 格式encoding="utf-8"

    df.to_csv('radiation_clean.csv', index=False, sep=';', encoding='utf-8')
    
  9. 使用to_parquet方法将相同的 DataFrame 导出为 Parquet 列式和二进制格式:

    df.to_parquet('radiation_clean.prq', index=False)
    

    注意

    转换 datetime 为字符串时要小心!

使用 Pandas 进行可视化

Pandas 可以被看作是一个数据的瑞士军刀,而数据科学家在分析数据时总是需要的一项技能就是可视化数据。我们将在后续详细介绍可应用于分析的各种图表类型。现在的目标是展示如何直接从 pandas 创建快速且简单的图表。

plot函数可以直接从 DataFrame 选择调用,实现快速可视化。通过使用 Matplotlib 并将数据从 DataFrame 传递到绘图函数,可以创建散点图。现在我们了解了工具,接下来让我们专注于 pandas 的数据处理接口。这个接口非常强大,其他一些项目(如 Spark)也复制了它。我们将在下一章更详细地解释图表的组成部分和方法。

你将在下一章看到如何创建对统计分析有用的图表。在这里,重点是了解如何从 pandas 创建图表以进行快速可视化。

活动 3:使用 Pandas 绘制数据

为了完成我们的活动,让我们重新做一遍之前的所有步骤,并用结果绘制图表,就像在初步分析中所做的那样:

  1. 使用我们之前处理过的 RadNet DataFrame。

  2. 修复所有的数据类型问题,正如我们之前所看到的。

  3. 创建一个按Location过滤的图表,选择San Bernardino城市,并选择一个放射性核素,x-轴为日期,y-轴为放射性核素I-131图 1.17:带 I-131 的地点图表

    图 1.17:带 I-131 的地点图表
  4. 创建一个散点图,显示两个相关放射性核素I-131I-132的浓度:

图 1.18:I-131 和 I-132 的图表

图 1.18:I-131 和 I-132 的图表

注意

本活动的解决方案可以在 203 页找到。

我们在这里稍微有些超前了,因此不必担心图表的细节,或者如何设置标题、标签等。这里的关键是理解我们可以直接从 DataFrame 绘制图表进行快速分析和可视化。

摘要

我们已经了解了数据分析和数据科学中最常用的 Python 库,它们组成了 Python 数据科学栈。我们学习了如何获取数据、选择数据、过滤数据并进行聚合。我们还学习了如何导出分析结果并生成一些快速图表。

这些步骤几乎适用于任何数据分析。这里展示的思路和操作可以应用于大数据的数据处理。Spark DataFrame 的创建考虑了 pandas 接口,许多操作在 pandas 和 Spark 中以非常相似的方式执行,这大大简化了分析过程。掌握 pandas 的另一个巨大优势是,Spark 可以将其 DataFrame 转换为 pandas DataFrame,然后再转换回来,使分析师能够使用最适合任务的工具。

在进入大数据之前,我们需要了解如何更好地可视化分析结果。如果我们使用正确的图表来可视化数据,我们对数据及其行为的理解可以大大增强。通过绘制数据,我们能够做出推断,并观察到异常和模式。

在下一章,我们将学习如何为每种数据和分析选择合适的图表,并如何使用 Matplotlib 和 Seaborn 绘制它。

第三章:第二章

统计可视化

学习目标

我们将从理解 Python 在数据处理和可视化方面的强大能力开始,创造出有用的分析结果。

本章结束时,你将能够:

  • 使用图表进行数据分析

  • 创建各种类型的图表

  • 更改图表参数,如颜色、标题和轴

  • 导出图表以用于展示、打印及其他用途

在本章中,我们将演示学生如何使用 Matplotlib 和 Seaborn 生成可视化图表。

引言

在上一章中,我们学习了最常用于数据科学的 Python 库。虽然这些库本身并不是大数据库,但 Python 数据科学堆栈的库(NumPyJupyterIPythonPandasMatplotlib)在大数据分析中非常重要。

正如本章所演示的,任何分析都离不开可视化,哪怕是大数据集也不例外,因此,掌握如何从数据中生成图像和图表,对于我们大数据分析的目标非常重要。在接下来的章节中,我们将演示如何处理大量数据并使用 Python 工具进行聚合和可视化。

Python 有多个可视化库,如 Plotly、Bokeh 等。但其中最古老、最灵活、使用最广泛的就是 Matplotlib。在详细讲解如何使用 Matplotlib 创建图表之前,我们先来了解哪些类型的图表对于分析是相关的。

图表的类型及其使用时机

每一次分析,无论是针对小型数据集还是大型数据集,都涉及到一个描述性统计步骤,在这个步骤中,数据通过均值、中位数、百分比和相关性等统计量进行汇总和描述。这个步骤通常是分析工作流程中的第一步,它有助于初步理解数据及其一般模式和行为,为分析师提出假设提供基础,并指导分析的下一步。图表是帮助此步骤的有力工具,它使分析师能够可视化数据,创建新的视图和概念,并将其传达给更广泛的受众。

关于可视化信息的统计学文献浩如烟海。Edward Tufte 的经典书籍《Envisioning Information》展示了如何以图形形式呈现信息的美丽而有用的例子。在另一本书《The Visual Display of Quantitative Information》中,Tufte 列举了一个用于分析和传递信息(包括统计数据)的图表应该具备的几个特征:

  • 显示数据

  • 避免扭曲数据所传达的信息

  • 使大型数据集具有可理解性

  • 具有合理清晰的目的——描述、探索、表格化或装饰

图表必须揭示信息。在创建分析时,我们应牢记这些原则来创建图表。

一个图表还应该能够独立于分析而突出表现。假设你正在撰写一份分析报告,这份报告变得非常详细。现在,我们需要对这份详细的分析做一个总结。为了使分析的要点清晰,图表可以用来表示数据。这个图表应该能够在没有整个详细分析的情况下支持这个总结。为了让图表能提供更多信息,并且能够在总结中独立存在,我们需要为它添加更多信息,例如标题和标签。

练习 8:绘制分析函数

在这个练习中,我们将使用 Matplotlib 库创建一个基本的图表,我们将可视化一个二元函数,例如,y = f(x),其中 f(x)

  1. 首先,创建一个新的 Jupyter Notebook 并导入所有必需的库:

    %matplotlib inline
    import pandas as pd
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    
  2. 现在,让我们生成一个数据集并使用以下代码绘制它:

    x = np.linspace(-50, 50, 100)
    y = np.power(x, 2)
    
  3. 使用以下命令创建一个基本的 Matplotlib 图表:

    plt.plot(x, y)
    

    输出结果如下:

    图 2.1:X 轴和 Y 轴的基本图表

    图 2.1:X 轴和 Y 轴的基本图表
  4. 现在,将数据生成函数从 修改为 ,保持相同的区间 [-50,50],并重新绘制线图:

    y_hat = np.power(x, 3)
    plt.plot(x, y_hat)
    

    输出结果如下:

图 2.2:X 轴和 Y 轴的基本图表

图 2.2:X 轴和 Y 轴的基本图表

如你所见,函数的形状发生了变化,正如预期的那样。我们使用的基本图表类型足以看到 yy_hat 值之间的变化。但仍然有一些问题:我们只绘制了一个数学函数,但通常我们收集的数据是有维度的,比如长度、时间和质量。我们如何将这些信息添加到图表中?我们如何添加标题?让我们在下一节中探讨这个问题。

图表的组成部分

每个图表都有一组可以调整的公共组成部分。Matplotlib 使用的这些组件名称将在下图中展示:

图 2.3:图表的组成部分

图 2.3:图表的组成部分

图表的组成部分如下:

  • 图形:图表的基础,所有其他组件都绘制在这里。

  • 坐标轴:包含图形元素并设置坐标系统。

  • 标题:标题给图表命名。

  • X 轴标签x 轴的名称,通常带有单位。

  • Y 轴标签y 轴的名称,通常带有单位。

  • 图例:图例是对图表中数据的描述,帮助你识别图中的曲线和点。

  • 刻度和刻度标签:它们表示图表中刻度上的参考点,数据值所在的位置。标签则表示具体的数值。

  • 线图:这些是与数据一起绘制的线条。

  • 标记:标记是用来标示数据点的图形符号。

  • 坐标轴:限定图表区域的线条,数据将在该区域中绘制。

这些组件中的每一个都可以根据当前可视化任务的需求进行配置。我们将逐一介绍每种图形类型以及如何调整之前描述的组件。

练习 9:创建一个图形

使用 Matplotlib 创建图形有多种方式。第一种方式与 MATLAB 的方法非常相似,称为 Pyplot。Pyplot 是一个 API,是 Matplotlib 的基于状态的接口,这意味着它会将配置和其他参数保存在对象中。Pyplot 被设计为一种简单的用法。

执行以下步骤,使用 Matplotlib 库绘制正弦函数图形:

  1. 导入所有需要的库,就像我们在前面的练习中做的那样:

    %matplotlib inline
    import pandas as pd
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    

    第二个 API,称为 plt.subplots 模块。

  2. 现在,要获取图形和坐标轴,请使用以下命令:

    fig, ax = plt.subplots()
    

    注意

    图形是所有其他图形组件的顶层容器。坐标轴设置了诸如轴、坐标系统等内容,并包含了图形元素,如线条、文本等。

  3. 要将一个图形添加到使用面向对象 API 创建的图表中,请使用以下命令:

    x = np.linspace(0,100,500)
    y = np.sin(2*np.pi*x/100)
    ax.plot(x, y)
    

    输出结果如下:

图 2.4:使用面向对象 API 绘制的图形输出

图 2.4:使用面向对象 API 绘制的图形输出

我们正在将一条线性图添加到属于 fig 图形的 ax 坐标轴中。图形的修改,如标签名称、标题等,将在本章稍后演示。现在,让我们看看如何创建我们可能在分析中使用的每种类型的图形。

练习 10:为数学函数创建图形

练习 1:绘制一个解析函数中,我们使用类似 MATLAB 的接口 Pyplot 为一个数学函数创建了图表。现在我们已经知道如何使用 Matplotlib 的面向对象 API,让我们使用它创建一个新图表。当使用面向对象 API 时,分析人员可以根据数据来源灵活地创建图表。

让我们使用面向对象 API 和 NumPy 的 sine 函数,在区间 [0,100] 上创建一个图形:

  1. 创建 x 轴的数据点:

    import numpy as np
    x = np.linspace(0,100,200)
    y = np.sin(x)
    
  2. 创建 Matplotlib 的 API 接口:

    %matplotlib inline
    import matplotlib.pyplot as plt
    fig, ax = plt.subplots()
    
  3. 使用坐标轴对象 ax 添加图表:

    ax.plot(x, y)
    

    输出结果如下:

    图 2.5:数学函数的图形

图 2.5:数学函数的图形

请注意,我们再次使用 linspace 函数在线性区间 [0, 100] 上创建了 200 个值,然后对这些值应用了 sine 函数,生成了 y 轴。这是在创建数据区间时常用的一种方法。

Seaborn

Seaborn (seaborn.pydata.org/) 是 PyData 工具家族的一部分,是一个基于 Matplotlib 的可视化库,旨在更轻松地创建统计图形。它可以直接操作 DataFrame 和 Series,进行内部的聚合和映射。Seaborn 使用颜色调色板和样式,使可视化更加一致且信息更丰富。它还具有一些可以计算统计数据的函数,例如回归、估计和误差。Seaborn 也能轻松创建一些特殊的图形,如小提琴图和多面图。

应该使用哪个工具?

Seaborn 尝试使一些常见分析图表的创建比直接使用 Matplotlib 更加简单。Matplotlib 可以被认为比 Seaborn 更低级,虽然这使得它有些繁琐和冗长,但它为分析师提供了更大的灵活性。有些图表,在 Seaborn 中只需调用一个函数即可创建,而在 Matplotlib 中可能需要几行代码才能实现。

没有明确的规则来确定分析师应该只使用 pandas 的绘图接口、直接使用 Matplotlib 还是使用 Seaborn。分析师应当牢记可视化需求以及创建所需图形所需的配置级别。

Pandas 的绘图接口更易于使用,但功能较为受限和局限。Seaborn 提供了多种现成的图表模式,包括常见的统计图表,如配对图和箱型图,但要求数据必须以整洁的格式进行整理,并且对图表的外观有较为明确的意见。Matplotlib 是两者的基础,比这两者更灵活,但要创建与其他两者相同的可视化效果,需要写更多的代码。

本书中的经验法则是:如何用最少的代码、且不改变数据的情况下,创建我需要的图形?有了这个原则,我们将使用三种选择,有时同时使用它们,以达到我们的可视化目标。分析师不应该局限于使用其中一种选项。我们鼓励使用任何能够创建有意义可视化的工具。

让我们来了解统计分析中最常见的几种图表类型。

图形类型

我们将展示的第一种图形是折线图线性图。折线图通过在两个坐标轴(xy)上连接的点来显示数据,通常是笛卡尔坐标系,并通常按 x 轴顺序排列。折线图用于展示数据的趋势,比如时间序列数据。

与折线图相关的图形是散点图。散点图通过笛卡尔坐标系将数据表示为点。通常,这个图表展示两个变量,尽管如果通过类别对数据进行颜色编码或大小编码,可能会传达更多的信息。散点图对于展示变量之间的关系和可能的相关性非常有用。

直方图用于表示数据的分布。与前两个示例不同,直方图通常只显示一个变量,通常在 x-轴上,而 y-轴显示数据的发生频率。创建直方图的过程比折线图和散点图要复杂一些,因此我们将更详细地解释它们。

箱线图也可用于表示频率分布,但它有助于通过一些统计量,如均值、中位数和标准差,比较数据组。箱线图用于可视化数据分布和异常值。

每种图表类型都有其应用,选择正确的图表类型对于分析的成功至关重要。例如,折线图可以用于展示过去一个世纪的经济增长趋势,而箱线图则很难用于此类分析。另一个常见的数据分析任务是识别变量之间的相关性:理解两个变量是否表现出相关的行为。散点图是常用的可视化工具。直方图对于可视化某个范围或区间内的数据点数量很有用,例如显示每加仑油耗在 10 到 20 英里之间的汽车数量。

折线图

如前一节所述,折线图通过连接数据点来展示数据。折线图非常适合展示趋势和倾向。在同一图表上可以绘制多条折线,用于比较各条线的行为,尽管必须确保图表上的单位一致。折线图还可以展示自变量和因变量之间的关系。一个常见的例子就是时间序列。

时间序列图

时间序列图顾名思义,用于展示数据相对于时间的变化。时间序列图在金融领域和环境科学中使用频繁。例如,以下图表展示了历史温度异常的时间序列:

图 2.6: 时间序列图

图 2.6: 时间序列图

来源

upload.wikimedia.org/wikipedia/commons/c/c1/2000_Year_Temperature_Comparison.png

通常,时间序列图的 time 变量位于 x-轴。

练习 11: 使用不同库创建折线图

让我们比较 Matplotlib、Pandas 和 Seaborn 之间的创建过程。我们将创建一个包含随机值的 Pandas DataFrame,并使用不同的方法进行绘制:

  1. 创建一个包含随机值的数据集:

    import numpy as np
    X = np.arange(0,100)
    Y = np.random.randint(0,200, size=X.shape[0])
    
  2. 使用 Matplotlib Pyplot 接口绘制数据:

    %matplotlib inline
    import matplotlib.pyplot as plt
    plt.plot(X, Y)
    
  3. 现在,让我们使用创建的值创建一个 Pandas DataFrame:

    import pandas as pd
    df = pd.DataFrame({'x':X, 'y_col':Y})
    
  4. 使用 Pyplot 接口绘制图表,但需要使用 data 参数:

    plt.plot('x', 'y_col', data=df)
    

    输出结果如下:

    图 2.7: 使用不同库的折线图

    图 2.7: 使用不同库的折线图
  5. 使用相同的 DataFrame,我们还可以直接从 Pandas DataFrame 绘制:

    df.plot('x', 'y_col')
    

    输出结果如下:

    图 2.8:来自 pandas DataFrame 的折线图

    图 2.8:来自 pandas DataFrame 的折线图
  6. 那么 Seaborn 怎么样?我们来使用 Seaborn 绘制相同的折线图:

    import seaborn as sns
    sns.lineplot(X, Y)
    sns.lineplot('x', 'y_col', data=df)
    

    输出结果如下:

图 2.9:来自 Seaborn DataFrame 的折线图

图 2.9:来自 Seaborn DataFrame 的折线图

我们可以看到,在这种情况下,Matplotlib 和 Seaborn 使用的接口非常相似。

Pandas DataFrames 和分组数据

正如我们在上一章中学习的,当分析数据并使用 Pandas 时,我们可以使用 Pandas 的 plot 函数或直接使用 Matplotlib。Pandas 在后台使用 Matplotlib,因此集成得非常好。根据具体情况,我们可以直接从 pandas 绘制,或者使用 Matplotlib 创建 figureaxes,并将其传递给 pandas 来绘制。例如,当进行 GroupBy 时,我们可以根据 GroupBy 键将数据分开。那么如何绘制 GroupBy 的结果呢?我们有几种方法可以选择。例如,如果 DataFrame 已经是正确格式,我们可以直接使用 pandas:

注意

以下代码是示例,无法执行。

fig, ax = plt.subplots()
df = pd.read_csv('data/dow_jones_index.data')
df[df.stock.isin(['MSFT', 'GE', 'PG'])].groupby('stock')['volume'].plot(ax=ax)

或者,我们可以将每个 GroupBy 键绘制在同一个图上:

fig, ax = plt.subplots()
df.groupby('stock').volume.plot(ax=ax)

在以下活动中,我们将使用上一章中学到的内容,从 URL 读取 CSV 文件并解析它。数据集是 Auto-MPG 数据集(raw.githubusercontent.com/TrainingByPackt/Big-Data-Analysis-with-Python/master/Lesson02/Dataset/auto-mpg.data)。

注意

该数据集是从 StatLib 库提供的数据集修改而来的。原始数据集可以在 auto-mpg.data-original 文件中找到。

数据涉及城市循环的每加仑燃油消耗,包含三种多值离散属性和五种连续属性。

活动 4:使用面向对象 API 和 Pandas DataFrame 绘制折线图

在本活动中,我们将从 Auto-MPG 数据集中创建一个时间序列折线图,作为使用 pandas 和面向对象 API 绘图的第一个示例。这种类型的图在分析中非常常见,帮助回答诸如“平均马力是否随着时间的推移增加或减少?”的问题。

现在,按照这些步骤使用 pandas 和面向对象 API 绘制每年平均马力的图:

  1. 将所需的库和包导入 Jupyter 笔记本。

  2. 将 Auto-MPG 数据集读入 Spark 对象。

  3. 提供列名以简化数据集,如下所示:

    column_names = ['mpg', 'cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'year', 'origin', 'name']
    
  4. 现在读取带列名的新数据集并显示它。

  5. horsepoweryear 数据类型转换为浮动和整数。

  6. 现在使用 pandas 绘制每年平均马力的图:

图 2.10:使用面向对象 API 和 Pandas DataFrame 绘制的折线图

图 2.10:使用面向对象 API 和 Pandas DataFrame 绘制的折线图

注意

此活动的解答可以在第 205 页找到。

请注意,我们使用的是来自 pandas 的绘图函数,但将我们通过 Matplotlib 创建的轴直接作为参数传递给这些函数。如前一章所述,虽然这不是必需的,但它可以让你在 pandas 之外配置图形,并在之后更改其配置。这种行为也适用于其他类型的图形。现在让我们来处理散点图。

散点图

为了理解两个变量之间的相关性,通常使用散点图,因为它们可以让我们看到点的分布。使用 Matplotlib 创建散点图与创建线图类似,但我们使用的是 scatter 方法,而不是 plot 方法。

让我们来看一个使用 Auto-MPG 数据集的例子(archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/):

fig, ax = plt.subplots()
ax.scatter(x = df['horsepower'], y=df['weight'])

图 2.11:使用 Matplotlib 库绘制的散点图

请注意,我们是直接从轴上调用散点图方法的。在 Matplotlib 的术语中,我们向属于 fig 图形的轴 ax 添加了一个散点图。我们还可以通过 Seaborn 很容易地向图中添加更多维度,比如 color 和点的 size

import seaborn as sns
sns.scatterplot(data=df, x='horsepower', y='weight', hue='cylinders', size='mpg')

图 2.12:使用 Seaborn 库绘制的散点图

图 2.12:使用 Seaborn 库绘制的散点图

如我们所见,散点图对于理解两个变量,甚至更多变量之间的关系非常有帮助。我们可以推断出,例如,horsepower(马力)和 weight(重量)之间存在正相关。通过散点图,我们还可以轻松地看到一个异常值,而使用其他类型的图形时,可能会更复杂。我们在折线图中看到的关于分组数据和 pandas DataFrame 的相同原则也适用于散点图。

我们可以通过 kind 参数直接从 pandas 生成散点图:

df.plot(kind='scatter', x='horsepower', y='weight')

创建一个图形并将其传递给 Pandas:

fig, ax = plt.subplots()
df.plot(kind='scatter', x='horsepower', y='weight', ax =ax)

活动 5:使用散点图理解变量之间的关系

为了继续我们的数据分析并学习如何绘制数据,让我们来看一个散点图可以帮助解答的问题。例如,使用散点图回答以下问题:

马力与重量之间是否存在关系?

为了回答这个问题,我们需要使用 Auto-MPG 数据创建一个散点图:

  1. 使用已经导入的 Auto-MPG 数据集。

    注意

    请参考前面的练习,了解如何导入数据集。

  2. 使用 Matplotlib 的面向对象 API:

    %matplotlib inline
    import matplotlib.pyplot as plt
    fig, ax = plt.subplots()
    
  3. 使用 scatter 方法创建一个散点图:

    ax.scatter(x = df['horsepower'], y=df['weight'])
    

    注意

    此活动的解答可以在第 208 页找到。

我们可以识别出马力与重量之间的大致线性关系,其中有一些异常值,它们的马力较高但重量较低。这种图形有助于分析师解读数据的行为。

直方图

直方图与我们迄今所见的图形稍有不同,因为它们只尝试可视化一个变量的分布,而不是两个或多个。直方图的目标是可视化一个变量的概率分布,换句话说,就是计算某些值在固定区间(或箱子)内出现的次数。

区间是连续且相邻的,但不需要具有相同的大小,尽管这种安排最为常见。

区间数和区间大小的选择更多依赖于数据和分析目标,而非任何固定的通用规则。区间数越大,每个区间的大小就越小(更窄),反之亦然。例如,当数据有很多噪声或变化时,少量的区间(较大的区间)可以显示数据的总体轮廓,从而减少噪声在初步分析中的影响。当数据的密度较高时,更多的区间会更有用。

练习 12:创建马力分布的直方图

在我们努力理解数据时,现在我们想看看所有汽车的马力分布。具有适当直方图的分析问题包括:一个变量的最频繁值是多少?分布是集中在中间还是有尾巴?让我们绘制一个马力分布的直方图:

  1. 在 Jupyter 笔记本中导入所需的库,并从 Auto-MPG 数据集仓库读取数据集:

    import pandas as pd
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import seaborn as sns
    url = "https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data"
    df = pd.read_csv(url)
    
  2. 提供列名称以简化数据集,如下所示:

    column_names = ['mpg', 'Cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'year', 'origin', 'name']
    
  3. 现在读取带有列名的新数据集并显示它:

    df = pd.read_csv(url, names= column_names, delim_whitespace=True)
    df.head()
    

    图形如下:

    图 2.13:自动 mpg 数据集

    图 2.13:自动 mpg 数据集
  4. 使用以下命令将horsepower(马力)和year(年份)数据类型转换为浮动和整数:

    df.loc[df.horsepower == '?', 'horsepower'] = np.nan
    df['horsepower'] = pd.to_numeric(df['horsepower'])
    df['full_date'] = pd.to_datetime(df.year, format='%y')
    df['year'] = df['full_date'].dt.year
    
  5. 使用plot函数和kind='hist'从 Pandas DataFrame 直接创建图形:

    df.horsepower.plot(kind='hist')
    

    图 2.14:直方图图

    图 2.14:直方图图
  6. 识别horsepower(马力)浓度:

    sns.distplot(df['weight'])
    

图 2.15:直方图浓度图

图 2.15:直方图浓度图

我们可以从这张图中看到,值的分布偏向左侧,比如在50100马力之间的汽车更多,而大于200马力的汽车较少。这对于理解一些数据在分析中的变化可能非常有用。

箱型图

箱型图也用于查看值的变化,但现在是每列内的变化。我们希望看到当按另一个变量分组时,值如何比较。例如,由于它们的格式,箱型图有时被称为胡须图箱型胡须图,因为从主框中垂直延伸出的线条:

图 2.16:箱型图

来源

en.wikipedia.org/wiki/File:Michelsonmorley-boxplot.svg

箱型图使用四分位数(第一和第三四分位数)来创建箱子和须。箱子中的线是第二四分位数——即中位数。须的定义可以有所不同,例如使用数据的平均值上下一个标准差,但通常使用 1.5 倍的四分位差(Q3 - Q1)从箱子的边缘开始。如果某个值超出这些范围,不论是上方还是下方,都将绘制为一个,通常被认为是异常值。

练习 13:使用箱型图分析缸数和马力的关系

有时我们不仅希望看到每个变量的分布,还希望看到所关注的变量相对于另一个属性的变化。例如,我们想知道给定缸数时,马力如何变化。让我们使用 Seaborn 创建一个箱型图,将马力分布与缸数进行比较:

  1. 在 Jupyter Notebook 中导入所需的库并从 Auto-MPG 数据集仓库读取数据集:

    %matplotlib inline
    import pandas as pd
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import seaborn as sns
    url = "https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data"
    df = pd.read_csv(url)
    
  2. 提供列名以简化数据集,如下所示:

    column_names = ['mpg', 'Cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'year', 'origin', 'name']
    
  3. 现在读取带有列名的新数据集并显示它:

    df = pd.read_csv(url, names= column_names, delim_whitespace=True)
    df.head()
    

    图形如下所示:

    图 2.17:auto-mpg 数据集

    图 2.17:auto-mpg 数据集
  4. 使用以下命令将马力和年份的数据类型转换为浮动型和整数型:

    df.loc[df.horsepower == '?', 'horsepower'] = np.nan
    df['horsepower'] = pd.to_numeric(df['horsepower'])
    df['full_date'] = pd.to_datetime(df.year, format='%y')
    df['year'] = df['full_date'].dt.year
    
  5. 使用 Seaborn 的 boxplot 函数创建一个箱型图:

    sns.boxplot(data=df, x="cylinders", y="horsepower")
    

    图 2.18:使用 Seaborn 箱型图函数

    图 2.18:使用 Seaborn 箱型图函数
  6. 现在,为了对比目的,直接使用 pandas 创建相同的boxplot

    df.boxplot(column='horsepower', by='cylinders')
    

图 2.19:使用 pandas 的箱型图

图 2.19:使用 pandas 的箱型图

在分析方面,我们可以看到,3 缸的马力变动范围小于 8 缸。我们还可以看到,6 缸和 8 缸的数据显示了异常值。至于绘图,Seaborn 函数更为完整,自动为不同的缸数显示不同的颜色,并将 DataFrame 列的名称作为标签显示在图表中。

更改图形设计:修改图形组件

到目前为止,我们已经查看了用于分析数据的主要图表,无论是直接展示还是按组展示,用于比较和趋势可视化。但我们可以看到的一点是,每个图形的设计都与其他图形不同,我们没有基本的元素,比如标题和图例。

我们已经了解到,图形由多个组件组成,例如图形标题xy 标签等。在使用 Seaborn 时,图形已经有了xy 标签,并且列的名称被作为标签显示。而使用 Matplotlib 时,我们没有这些。这样的变化不仅仅是外观上的。

除了标签和标题外,通过调整线宽、颜色和点大小等因素,我们可以大大改善图形的理解性。一个图形必须能够独立存在,因此标题、图例和单位至关重要。我们如何应用前面描述的概念,在 Matplotlib 和 Seaborn 中制作出好的、富有信息的图形呢?

绘图的配置方式多种多样,选择极为丰富。Matplotlib 在配置方面功能强大,但也牺牲了简便性。在使用 Matplotlib 更改图形的某些基本参数时可能会显得笨重,而这时 Seaborn 和其他库可以提供帮助。但在某些情况下,这种方式是值得的,例如在自定义图形时,因此在技术栈中具备此类能力是必要的。我们将在本节中重点讨论如何更改一些基本的绘图参数。

坐标轴对象的标题和标签配置

如前所述,Matplotlib 的面向对象 API 提供了更大的灵活性。让我们在接下来的练习中探讨如何配置坐标轴对象的标题和标签。

练习 14:配置坐标轴对象的标题和标签

执行以下步骤以配置标题和坐标轴对象的标签。我们将从上一个练习继续,并按照这些步骤进行操作:

  1. 通过调用 set 方法设置 title、x 轴标签和 y 轴标签:

    import matplotlib.pyplot as plt
    fig, ax = plt.subplots()
    ax.set(title="Graph title", xlabel="Label of x axis (units)", ylabel="Label of y axis (units)")
    ax.plot()
    

    绘图如下:

    图 2.20:配置标题和标签

    图 2.20:配置标题和标签
  2. 图例可以通过外部传递(仅使用 Matplotlib 时),也可以在 Pandas 绘图时设置,并与坐标轴一起绘制。使用以下命令绘制图例:

    fig, ax = plt.subplots()
    df.groupby('year')['horsepower'].mean().plot(ax=ax, label='horsepower')
    ax.legend()
    

    绘图如下:

    图 2.21:带有图例的折线图

    图 2.21:带有图例的折线图
  3. 绘制图例的替代方法如下:

    fig, ax = plt.subplots()
    df.groupby('year')['horsepower'].mean().plot(ax=ax)
    ax.legend(['horsepower'])
    

    绘图如下:

图 2.22:带有图例的折线图(替代方法)

图 2.22:带有图例的折线图(替代方法)

线条样式和颜色

对于折线图,可以通过 lslwmarkercolor 参数来配置线条的颜色、粗细、标记和样式:

df.groupby('year')['horsepower'].mean().plot(ls='-.', color='r', lw=3)

图 2.23:带有颜色和样式的折线图

图 2.23:带有颜色和样式的折线图

图形大小

我们还可以配置图形的大小。figsize 参数可以作为一个元组(x 轴,y 轴)传递给所有绘图函数,单位为英寸:

df.plot(kind='scatter', x='weight', y='horsepower', figsize=(20,10))

图 2.24:更大图形大小的绘图

图 2.24:更大图形大小的绘图

练习 15:使用 Matplotlib 样式表

Matplotlib 有一些样式表,定义了图形的一般规则,如背景 颜色刻度线图形颜色调色板。假设我们想更改样式,以便我们的图形在打印时具有更好的颜色。为此,按照以下步骤操作:

  1. 首先使用以下命令打印可用样式列表:

    import matplotlib.pyplot as plt
    print(plt.style.available)
    

    输出如下:

    ['bmh', 'classic', 'dark_background', 'fast', 'fivethirtyeight', 'ggplot', 'grayscale', 'seaborn-bright', 'seaborn-colorblind', 'seaborn-dark-palette', 'seaborn-dark', 'seaborn-darkgrid', 'seaborn-deep', 'seaborn-muted', 'seaborn-notebook', 'seaborn-paper', 'seaborn-pastel', 'seaborn-poster', 'seaborn-talk', 'seaborn-ticks', 'seaborn-white', 'seaborn-whitegrid', 'seaborn', 'Solarize_Light2', 'tableau-colorblind10', '_classic_test']
    
  2. 现在,让我们使用 classic 样式创建一个散点图。在继续之前,确保先导入 Matplotlib 库:

    %matplotlib inline
    import numpy as np
    import matplotlib.pyplot as plt
    url = ('https://raw.githubusercontent.com/TrainingByPackt/Big-Data-Analysis-with-Python/master/Lesson02/Dataset/auto-mpg.data')
    df = pd.read_csv(url)
    column_names = ['mpg', 'cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'year', 'origin', 'name']
    df = pd.read_csv(url, names= column_names, delim_whitespace=True)
    df.loc[df.horsepower == '?', 'horsepower'] = np.nan
    df['horsepower'] = pd.to_numeric(df['horsepower'])
    plt.style.use(['classic'])
    df.plot(kind='scatter', x='weight', y='horsepower')
    

    输出如下:

图 2.25:使用 classic 样式的散点图

注意

要使用样式表,请使用以下命令:

plt.style.use('presentation')

Seaborn 在导入时所做的其中一项更改是将一些样式添加到可用样式列表中。样式在为不同受众创建图像时也非常有用,例如为笔记本中的可视化创建一个样式,为打印或演示创建另一个样式。

导出图表

在生成可视化并配置好细节后,我们可以将图表导出为硬拷贝格式,如 PNG、JPEG 或 SVG。如果我们在笔记本中使用交互式 API,我们只需在 pyplot 接口上调用 savefig 函数,最后生成的图表将被导出到文件:

df.plot(kind='scatter', x='weight', y='horsepower', figsize=(20,10))
plt.savefig('horsepower_weight_scatter.png')

图 2.26:导出图表

图 2.26:导出图表

所有的图表配置将传递到 plot。要在使用面向对象的 API 时导出图表,我们可以从图形中调用 savefig

fig, ax = plt.subplots()
df.plot(kind='scatter', x='weight', y='horsepower', figsize=(20,10), ax=ax)
fig.savefig('horsepower_weight_scatter.jpg')

图 2.27:保存图表

图 2.27:保存图表

我们可以更改一些保存图像的参数:

  • dpi:调整保存的图像分辨率。

  • facecolor:图形的面颜色。

  • edgecolor:图形周围的边框颜色。

  • format:通常为 PNG、PDF、PS、EPS、JPG 或 SVG。根据文件名扩展名推断。

Seaborn 也使用相同的底层 Matplotlib 机制来保存图形。直接从 Seaborn 图表中调用 savefig 方法:

sns_scatter = sns.scatterplot(data=df, x='horsepower', y='weight', hue='cylinders', size='mpg')
plt.savefig('scatter_fig.png', dpi=300)

图 2.28:使用 savefig 方法绘制图表

图 2.28:使用 savefig 方法绘制图表

通过这些补充选项,分析人员可以为不同的受众生成可视化内容,无论是在笔记本中、网站上,还是打印出来。

活动 6:将图表导出到磁盘文件

将我们的工作保存到文件中是一个很好的方法,可以使结果在不同媒介中共享。如果我们想要将其留作未来参考,这也是一个好方法。让我们创建一个图表并保存到磁盘:

  1. 导入 Auto-MPG 数据集。

  2. 使用 Matplotlib 面向对象的 API 创建任何类型的图表。例如,这里是一个体重的直方图:

    %matplotlib inline
    import matplotlib.pyplot
    fig, ax = plt.subplots()
    df.weight.plot(kind='hist', ax=ax)
    
  3. 使用 savefig 函数将其导出为 PNG 文件:

    fig.savefig('weight_hist.png')
    

    注意

    本活动的解决方案可以在第 209 页找到。

活动 7:完整的图表设计

为了让我们的图表独立存在,与分析分离,我们需要在图表中添加更多信息,以便其他分析人员或用户能够理解图表的内容,并明白所表示的内容。我们现在将结合本章所学,创建一个完整的图表,包括标题、标签和图例,并调整图表大小。

作为分析师,我们希望了解每年平均每加仑英里数是否增加,并希望按气缸数进行分组。例如,三缸车的燃油消耗随时间变化如何?它的表现是高于还是低于四缸车?

按照以下步骤创建我们的最终图表:

  1. 导入 Auto-MPG 数据集。

  2. yearcylinders进行groupby操作,并取消将它们作为索引的选项。

  3. 计算分组后的平均每加仑英里数,并将年份设置为索引。

  4. 将年份设置为数据框的索引。

  5. 使用面向对象的 API 创建图形和坐标轴。

  6. df_g数据集上按气缸数进行groupby操作,并使用大小为(10,8)的坐标轴绘制每加仑英里数变量。

  7. 在坐标轴上设置标题x轴标签和y轴标签。

  8. 在图表中包含图例。

  9. 将图形保存为 PNG 文件到磁盘。

    注意

    这个活动的解决方案可以在第 211 页找到。

我们可以从这张图表中推断出,四缸车比八缸车更为经济。我们还可以推断出,在研究期间,所有汽车的燃油效率都有所提高,1980 到 1982 年间,四缸车的燃油效率有所下降。

注意

注意,借助标签轴和图例,使用 Pandas 所做的复杂转换(按组汇总和平均,之后设置索引)在最终结果中变得容易解释。

总结

在本章中,我们已经看到,在分析数据时,创建有意义且有趣的可视化图形的重要性。一张好的数据可视化图能够极大地帮助分析师的工作,以一种能够触及更广泛受众的方式呈现数据,并解释那些可能难以用文字表达或用表格展示的概念。

一张有效的图表,作为数据可视化工具,必须展示数据,避免失真,使理解大型数据集变得容易,并有明确的目的,比如描述或探索。图表的主要目标是传达数据,因此分析师在创建图表时必须牢记这一点。一张有用的图表比一张美丽的图表更为重要。

我们展示了一些在分析中常用的图表类型:折线图、散点图、直方图和箱形图。每种图表都有其目的和应用,取决于数据和目标。我们还展示了如何直接从 Matplotlib、从 pandas,或是两者结合使用 Matplotlib 的 API(Pyplot、交互式 API 和面向对象 API)来创建图表。我们以解释如何更改图表外观的选项(从线条样式到标记和颜色),以及如何将图表保存为打印或共享的硬拷贝格式,结束了本章。

这里我们没有覆盖的还有许多配置图形的方式。可视化是一个庞大的领域,工具也提供了大量的选项。

在接下来的章节中,我们将重点讨论数据处理,包括使用 Hadoop 和 Spark 操作大规模数据。在学习这两种工具的基础知识后,我们将回到分析过程,其中将包含各种形式的图表。

第四章:第三章

使用大数据框架

学习目标

到本章结束时,你将能够:

  • 解释 HDFS 和 YARN Hadoop 组件

  • 执行 HDFS 文件操作

  • 比较 pandas DataFrame 和 Spark DataFrame

  • 使用 Spark 从本地文件系统和 HDFS 中读取文件

  • 使用 Spark 以 Parquet 格式写入文件

  • 编写分区文件以便快速分析

  • 使用 Spark 操作非结构化数据

在本章中,我们将探讨大数据工具,如 Hadoop 和 Spark。

介绍

在前几章中,我们看到如何使用 pandas 和 Matplotlib 进行数据操作与可视化,以及 Python 数据科学栈中的其他工具。到目前为止,我们使用的数据集相对较小,且结构相对简单。现实中的数据集可能比单台机器的内存容量要大几个数量级,处理这些数据集所需的时间也可能很长,常用的软件工具可能无法胜任。这就是通常定义的大数据:一种无法装入内存,或者无法在合理的时间内通过常规软件方法进行处理或分析的数据量。对某些人来说是大数据的东西,对于其他人来说可能不是大数据,这一定义可能因提问对象的不同而有所变化。

大数据还与 3V(后扩展为 4V)相关联:

  • 容量:顾名思义,大数据通常与非常大的数据量相关联。什么是“大”取决于上下文:对于一个系统,千兆字节就可以算作大数据,而对于另一个系统,则可能需要达到 PB 级数据。

  • 多样性:通常,大数据与不同的数据格式和类型相关联,如文本、视频和音频。数据可以是结构化的,如关系表,或是非结构化的,如文本和视频。

  • 速度:数据生成和存储的速度比其他系统更快,并且生产速度更加连续。流数据可以通过像电信运营商、在线商店甚至 Twitter 这样的平台生成。

  • 真实性:这一点是后来加入的,旨在说明在任何分析工作中,了解正在使用的数据及其含义非常重要。我们需要检查数据是否与我们预期的相符,转换过程是否改变了数据,数据是否反映了所收集的内容。

但让大数据具有吸引力的一个方面是分析组件:大数据平台旨在支持对这些庞大数据集进行分析和信息提取。这一章从这里开始:我们将学习如何使用两个最常见、最灵活的大数据框架——Hadoop 和 Spark——来操作、存储和分析大数据集。

Hadoop

Apache Hadoop 是一个为大规模数据的并行存储和计算而创建的软件组件集合。它在最初的设计理念是使用普通计算机进行分布式处理,具备高容错性和分布式计算能力。随着 Hadoop 的成功,越来越多的高端计算机开始被用于 Hadoop 集群,尽管普通硬件仍然是常见的使用案例。

并行存储是指使用多个通过网络互联的节点,以并行方式存储和检索数据的任何系统。

Hadoop 由以下组件组成:

  • Hadoop Common:基础的 Hadoop 通用项目

  • Hadoop YARN:资源和作业管理器

  • Hadoop MapReduce:一个大规模并行处理引擎

  • Hadoop 分布式文件系统HDFS):顾名思义,HDFS 是一个可以分布在多台计算机上的文件系统,通过使用本地磁盘,创建一个大规模的存储池:

图 3.1:HDFS 架构

图 3.1:HDFS 架构

另一个重要组件是YARNYet Another Resource Negotiator),是 Hadoop 的资源管理器和作业调度器。它负责管理提交到 Hadoop 集群的作业,根据所需和可用资源分配内存和 CPU。

Hadoop 普及了一种并行计算模型,叫做 MapReduce,这是 Google 最初开发的一种分布式计算范式。在 Hadoop 中直接运行使用 MapReduce 的程序是可能的。但自从 Hadoop 诞生以来,已经开发出了其他并行计算范式和框架(如 Spark),因此 MapReduce 在数据分析中并不常用。在深入了解 Spark 之前,让我们先看看如何在 HDFS 上操作文件。

使用 HDFS 操作数据

HDFS 是一个分布式文件系统,其一个重要特点是:它被设计用来运行在成千上万台并非专门为此设计的计算机上——也就是所谓的商品硬件。它不需要特殊的网络设备或特殊硬盘,可以运行在普通硬件上。HDFS 的另一个理念是容错性:硬件总会发生故障,因此 HDFS 通过高度容错的方式绕过故障,而不是试图避免它。它假定在大规模环境下会发生故障,因此 HDFS 实现了故障检测机制,以实现快速和自动恢复。它也具有可移植性,能在不同平台上运行,并且可以容纳单个文件,数据容量达到 TB 级。

从用户的角度来看,HDFS 的一个大优点是它支持传统的层次化文件结构组织(文件夹和文件的树形结构),因此用户可以在每一层创建文件夹和文件,简化了使用和操作。文件和文件夹可以移动、删除和重命名,因此用户无需了解数据复制或NameNode/DataNode架构即可使用 HDFS;它看起来与 Linux 文件系统类似。在演示如何访问文件之前,我们需要先解释一下访问 Hadoop 数据所使用的地址。例如,访问 HDFS 中文件的 URI 格式如下:

hdfs://hadoopnamenode.domainname/path/to/file

其中,namenode.domainname是 Hadoop 中配置的地址。Hadoop 用户指南(exitcondition.com/install-hadoop-windows/)详细介绍了如何访问 Hadoop 系统的不同部分。让我们通过几个例子更好地理解这一切是如何工作的。

练习 16:在 HDFS 中操作文件

假设一个分析师刚刚收到了一个要分析的大型数据集,并且它存储在 HDFS 系统中。这个分析师如何列出、复制、重命名和移动这些文件?假设分析师收到了一个名为new_data.csv的原始数据文件:

  1. 如果您使用的是 Linux 系统,请在终端中使用以下命令,或者如果使用 Windows 系统,请在命令提示符下执行此命令,以开始检查当前的目录和文件:

    hdfs dfs -ls /
    
  2. 我们在本地磁盘上有一个名为new_data.csv的文件,我们希望将其复制到 HDFS 数据文件夹中:

    hdfs dfs -put C:/Users/admin/Desktop/Lesson03/new_data.csv /
    
  3. 注意,命令的最后部分是 HDFS 中的路径。现在,使用mkdir命令在 HDFS 中创建一个文件夹:

    hdfs dfs -mkdir /data
    
  4. 然后将文件移动到 HDFS 中的数据文件夹:

    hdfs dfs -mv /data_file.csv /data
    
  5. 更改 CSV 文件的名称:

    hdfs dfs -mv /data/new_data.csv /data/other_data.csv
    
  6. 使用以下命令检查文件是否存在于当前路径:

    hadoop fs -ls /data
    

    输出结果如下:

    other_data.csv
    

    注意

    HDFS 部分之后的命令与 Linux Shell 中的命令相同。

了解如何在 HDFS 中操作文件和目录是大数据分析的重要部分,但通常,直接操作仅限于数据摄取时进行。要分析数据,HDFS 通常不会直接使用,像 Spark 这样的工具更为强大。让我们按顺序看看如何使用 Spark。

Spark

Sparkspark.apache.org)是一个用于大规模数据处理的统一分析引擎。Spark 最初是由加利福尼亚大学伯克利分校于 2009 年发起的项目,并于 2013 年移交给 Apache 软件基金会。

Spark 的设计旨在解决使用 Hadoop 架构进行分析时的一些问题,如数据流、SQL 操作存储在 HDFS 上的文件和机器学习。它可以将数据分布到集群中的所有计算节点,以减少每个计算步骤的延迟。Spark 的另一个特点是它的灵活性:它有适用于 Java、Scala、SQL、R 和 Python 的接口,以及适用于不同问题的库,例如用于机器学习的 MLlib、用于图计算的 GraphX 和用于流式工作负载的 Spark Streaming。

Spark 使用工作节点抽象,具有一个接收用户输入以启动并行执行的驱动进程,以及在集群节点上执行任务的工作进程。它具有内置的集群管理工具,并支持其他工具,如 Hadoop YARN、Apache Mesos(甚至 Kubernetes),可以集成到不同的环境和资源分配场景中。

Spark 也可以非常快速,因为它首先尝试将数据分布到所有节点,并将其保留在内存中,而不是仅仅依赖磁盘上的数据。它可以处理比所有可用内存总和还大的数据集,通过在内存和磁盘之间切换数据,但这个过程会比将整个数据集完全适配到所有节点的内存中时慢。

图 3.2:Spark 工作机制

图 3.2:Spark 工作机制

其他显著优势是,Spark 具有适用于多种本地和分布式存储系统的接口,如 HDFS、Amazon S3、Cassandra 等;可以通过 JDBC 或 ODBC 连接器连接到 RDBMS,如 PostgreSQL 和 MySQL;还可以使用Hive Metastore直接对 HDFS 文件运行 SQL。CSV、Parquet 和 ORC 等文件格式也可以被 Spark 直接读取。

这种灵活性在处理大数据源时非常有帮助,因为大数据源可能具有不同的格式。

Spark 可以作为交互式 Shell 使用,支持 Scala、Python 和 R,也可以作为作业提交平台使用,使用 spark-submit命令将作业分发到 Spark 集群。submit 方法用于将作业调度到 Spark 集群,该作业是通过脚本编码的。Spark 的 Python 接口被称为 PySpark,可以直接从终端访问,使用默认的 Python 版本;也可以通过 IPython shell 或在 Jupyter 笔记本内访问。

Spark SQL 和 Pandas DataFrames

RDD(弹性分布式数据集)是 Spark 用来处理数据的基础抽象。从 Spark 2.0 版本开始,推荐使用的 API 是 DataFrame API。DataFrame API 是在 RDD API 之上构建的,尽管仍然可以访问 RDD API。

使用 RDD 被认为是低级的,所有操作都可以在 DataFrame API 中实现,但学习一些关于 RDD API 的内容也无妨。

SQL 模块使用户能够使用 SQL 查询在 Spark 中查询数据,类似于常见的关系型数据库。DataFrame API 是 SQL 模块的一部分,处理结构化数据。这个数据接口有助于创建额外的优化,使用相同的执行引擎,无论使用什么 API 或语言来表达这些计算。

DataFrame API 类似于 Pandas DataFrame。在 Spark 中,DataFrame 是一个分布式的数据集合,按列组织,每一列都有一个名称。随着 Spark 2.0 的发布,DataFrame 成为更通用的 Dataset API 的一部分,但由于该 API 仅适用于 Java 和 Scala 语言,我们将只讨论 DataFrame API(在文档中称为 Untyped Dataset Operations)。

Spark DataFrame 的接口类似于 pandas 接口,但也有一些重要的区别:

  • 第一个区别是 Spark DataFrame 是 不可变的:创建后无法修改。

  • 第二个区别是 Spark 有两种不同的操作:转换行动

    转换 是应用于 DataFrame 元素的操作,并且是排队待执行的,数据尚未被提取。

    只有在调用 行动 时,数据才会被提取,并且所有排队的转换操作都会执行。这叫做延迟计算。

练习 17:在 Spark 中执行 DataFrame 操作

让我们开始使用 Spark 执行输入/输出和简单的聚合操作。正如我们之前所说,Spark 的接口灵感来自 pandas 接口。在 第二章使用 Matplotlib 和 Seaborn 的统计可视化 中学到的内容可以应用到这里,从而加速执行更复杂的分析,包括聚合、统计、计算和对聚合数据的可视化。我们希望读取一个 CSV 文件,就像我们之前做的那样,对其进行一些分析:

  1. 首先,在 Jupyter notebook 中使用以下命令创建一个 Spark 会话:

    from pyspark.sql import SparkSession
    >>> spark = SparkSession \
        .builder \
        .appName(“Python Spark Session”) \
        .getOrCreate()
    
  2. 现在,让我们使用以下命令从 mydata.csv 文件中读取数据:

    df = spark.read.csv(‘/data/mydata.csv’, header=True)
    
  3. 如前所述,Spark 的计算是延迟的,因此如果我们想显示 DataFrame 中的值,我们需要调用行动,如这里所示:

    df.show()
    +------+----+-------+
    |  name| age| height|
    +------+----+-------+
    |  Jonh|  22|   1.80|
    |Hughes|  34|   1.96|
    |  Mary|  27|   1.56|
    +------+----+-------+
    

    注意

    这在 pandas 中不是必需的:直接打印 DataFrame 就能显示内容。

练习 18:使用 Spark 访问数据

在读取 DataFrame 并显示其内容后,我们希望开始操作数据,以便进行分析。我们可以使用相同的 NumPy 选择语法来访问数据,提供列名作为 Column

  1. 让我们选择上一练习中导入的 DataFrame 中的某一列:

    df[‘age’].Column[‘age’]
    

    这与我们在 pandas 中看到的有所不同。选择 Spark DataFrame 中列的值的方法是 select。那么,让我们看看当我们使用这个方法时会发生什么。

  2. 再次使用相同的 DataFrame,使用 select 方法选择名称列:

    df.select(df[‘name’])DataFrame[age: string]
    
  3. 现在,它从 Column 变成了 DataFrame。因此,我们可以使用 DataFrame 的方法。使用 show 方法显示 select 方法对 age 列的结果:

    df.select(df[‘age’]).show()
    +---+
    |age|
    +---+
    | 22|
    | 34|
    | 27|
    +---+
    
  4. 让我们选择多个列。我们可以使用列的名称来做到这一点:

    df.select(df[‘age’], df[‘height’]).show()
    +---+------+
    |age|height|
    +---+------+
    | 22|  1.80|
    | 34|  1.96|
    | 27|  1.56|
    +---+------+
    

这对于其他列是可扩展的,通过名称选择,语法相同。我们将在下一章中讨论更复杂的操作,例如 带有 GroupBy 的聚合

练习 19:从本地文件系统和 HDFS 读取数据

如前所述,要从本地磁盘读取文件,只需将路径提供给 Spark。我们还可以读取位于不同存储系统中的其他文件格式。Spark 可以读取以下格式的文件:

  • CSV

  • JSON

  • ORC

  • Parquet

  • 文本

并且可以从以下存储系统中读取:

  • JDBC

  • ODBC

  • Hive

  • S3

  • HDFS

基于 URL 方案,作为练习,我们来读取来自不同位置和格式的数据:

  1. 在 Jupyter Notebook 中导入必要的库:

    from pyspark.sql import SparkSession
    spark = SparkSession \
        .builder \
        .appName(“Python Spark Session”) \
        .getOrCreate()
    
  2. 假设我们需要从一个 JSON 文件中获取一些数据,这对于从网络 API 收集的数据很常见。要直接从 HDFS 读取文件,请使用以下 URL:

    df = spark.read.json(‘hdfs://hadoopnamenode/data/myjsonfile.json’)
    

    请注意,使用这种 URL 时,我们必须提供 HDFS 端点的完整地址。我们也可以只使用简化路径,前提是 Spark 已经配置了正确的选项。

  3. 现在,使用以下命令将数据读取到 Spark 对象中:

    df = spark.read.json(‘hdfs://data/myjsonfile.json’)
    
  4. 因此,我们在 read 方法中选择格式,并在访问 URL 中选择存储系统。对于 JDBC 连接也使用相同的方法,但通常我们需要提供用户名和密码来连接。让我们看看如何连接到 PostgreSQL 数据库:

    url = “jdbc:postgresql://posgreserver:5432/mydatabase”
    properties = {“user”: “my_postgre_user”,  password: “mypassword”, “driver”: “org.postgresql.Driver”}
    df = spark.read.jdbc(url, table = “mytable”, properties = properties)
    

练习 20:将数据写回 HDFS 和 PostgreSQL

正如我们在使用 pandas 时看到的那样,执行一些操作和转换后,假设我们想将结果写回本地文件系统。当我们完成分析并希望与其他团队共享结果时,或者我们希望使用其他工具展示数据和结果时,这非常有用:

  1. 我们可以直接在 HDFS 上使用 write 方法从 DataFrame 写入:

    df.write.csv(‘results.csv’, header=True)
    
  2. 对于关系型数据库,使用与这里演示的相同 URL 和属性字典:

    df = spark.write.jdbc(url, table = “mytable”, properties = properties)
    

    这使得 Spark 在处理大型数据集并将其组合进行分析时具有极大的灵活性。

    注意

    Spark 可以作为一个中间工具来转换数据,包括聚合或修复数据问题,并以不同的格式保存供其他应用使用。

写入 Parquet 文件

Parquet 数据格式(parquet.apache.org/)是一种二进制的列式存储格式,可以被不同的工具使用,包括 Hadoop 和 Spark。它被构建以支持压缩,从而实现更高的性能和存储利用率。它的列式设计有助于在性能上进行数据选择,因为只会检索所需列的数据,而不是在不需要的行中查找并丢弃值,从而减少了大数据场景下的检索时间,在这些场景中数据是分布式并存储在磁盘上。Parquet 文件也可以通过外部应用程序读取和写入,使用 C++ 库,甚至可以直接从 pandas 中操作。

Parquet 库目前正在与 Arrow 项目arrow.apache.org/)一起开发。

在 Spark 中考虑更复杂的查询时,将数据存储为 Parquet 格式可以提高性能,特别是当查询需要搜索大规模数据集时。压缩有助于减少在 Spark 执行操作时需要传输的数据量,从而降低网络 I/O。它还支持模式和嵌套模式,类似于 JSON,Spark 可以直接从文件中读取模式。

Spark 中的 Parquet 写入器有几个选项,例如模式(追加、覆盖、忽略或错误,默认为错误)和压缩,选择压缩算法的参数。可用的算法如下:

  • gzip

  • lzo

  • brottli

  • lz4

  • 快速压缩

  • 未压缩

默认算法是 snappy

练习 21:写入 Parquet 文件

假设我们收到大量的 CSV 文件,需要对其进行一些分析,并且还需要减小数据体积。我们可以使用 Spark 和 Parquet 来实现。在开始分析之前,让我们将 CSV 文件转换为 Parquet 格式:

  1. 首先,从 HDFS 读取 CSV 文件:

    df = spark.read.csv(‘hdfs:/data/very_large_file.csv’, header=True)
    
  2. 将 DataFrame 中的 CSV 文件以 Parquet 格式写回到 HDFS:

    df.write.parquet(‘hdfs:/data/data_file’, compression=”snappy”)
    
  3. 现在将 Parquet 文件读取到一个新的 DataFrame 中:

    df_pq = spark.read.parquet(“hdfs:/data/data_file”)
    

    注意

    write.parquet 方法会创建一个名为 data_file 的文件夹,并生成一个长文件名的文件,例如 part-00000-1932c1b2-e776-48c8-9c96-2875bf76769b-c000.snappy.parquet

使用 Parquet 和分区提高分析性能

Parquet 支持并且也能提高查询性能的一个重要概念是分区。分区的想法是将数据拆分成可以更快速访问的部分。分区键是一个列,其值用于拆分数据集。分区在数据中存在有意义的划分时非常有用,这些划分可以单独处理。例如,如果你的数据是基于时间间隔的,则分区列可以是年份值。这样,当查询使用基于年份的筛选值时,只会读取与请求年份匹配的分区中的数据,而不是整个数据集。

分区也可以是嵌套的,并通过 Parquet 中的目录结构表示。所以,假设我们还想按月份列进行分区,那么 Parquet 数据集的文件夹结构将类似于以下形式:

hdfs -fs ls /data/data_file
year=2015
year=2016
year=2017
hdfs -fs ls /data/data_file/year=2017
month=01
month=02
month=03
month=04
month=05

当对分区进行过滤时,分区可以提高性能,因为只会读取所选分区中的数据,从而提高性能。要保存分区文件,应该使用 partitionBy 选项,可以在 parquet 命令中使用,或者将前一个命令与写入操作链式调用:

df.write.parquet(“hdfs:/data/data_file_partitioned”, partitionBy=[“year”, “month”])

另一种方法是:

df.write.partittionBy([“year”, “month”]).format(“parquet”).save(“hdfs:/data/data_file_partitioned”)

后者格式可以与前面的操作一起使用。读取分区数据时,Spark 可以根据目录结构推断分区结构。

如果正确使用分区,分析人员可以显著提高查询性能。但如果没有正确选择分区列,分区反而可能会影响性能。例如,如果数据集中只有一年的数据,按年份分区就没有任何好处。如果某列有太多不同的值,按该列进行分区也可能会产生问题,导致创建过多的分区,无法提升速度,甚至可能降低性能。

练习 22:创建分区数据集

在我们的初步分析中,我们发现数据中包含日期列,其中一个表示年份,一个表示月份,另一个表示日期。我们将对这些数据进行汇总,以获取每年、每月和每天的最小值、平均值和最大值。让我们从数据库中创建一个保存为 Parquet 格式的分区数据集:

  1. 定义一个 PostgreSQL 连接:

    url = “jdbc:postgresql://posgreserver:5432/timestamped_db”
    properties = {“user”: “my_postgre_user”,  password: “mypassword”, “driver”: “org.postgresql.Driver”}
    
  2. 使用 JDBC 连接器从 PostgreSQL 读取数据到 DataFrame:

    df = spark.read.jdbc(url, table = “sales”, properties = properties)
    
  3. 接下来,我们将把这些数据转换为分区 Parquet 格式:

    df.write.parquet(“hdfs:/data/data_file_partitioned”, partitionBy=[“year”, “month”, “day”], compression=”snappy”)
    

使用 Spark 作为不同数据源的中介,并考虑其数据处理和转换能力,使其成为结合和分析数据的优秀工具。

处理非结构化数据

非结构化数据通常指没有固定格式的数据。例如,CSV 文件是结构化的,而 JSON 文件也可以被认为是结构化的,尽管它不是表格形式。另一方面,计算机日志没有固定结构,不同的程序和守护进程会输出没有共同模式的消息。图像也是另一种非结构化数据的例子,类似于自由文本。

我们可以利用 Spark 在读取数据时的灵活性,解析非结构化格式并将所需信息提取到更结构化的格式中,便于分析。这一步通常称为 预处理数据清洗

练习 23:解析文本并清洗数据

在本练习中,我们将读取一个文本文件,将其拆分成行,并从给定的字符串中移除 thea 这两个词:

  1. 使用text方法将文本文件shake.txtraw.githubusercontent.com/TrainingByPackt/Big-Data-Analysis-with-Python/master/Lesson03/data/shake.txt)读入 Spark 对象:

    from operator import add
    rdd_df = spark.read.text(“/shake.txt”).rdd
    
  2. 使用以下命令从文本中提取行:

    lines = rdd_df.map(lambda line: line[0])
    
  3. 这将把文件中的每一行拆分为列表中的一个条目。要检查结果,可以使用collect方法,它会将所有数据收集到驱动程序进程中:

    lines.collect()
    
  4. 现在,让我们使用count方法计算行数:

    lines.count()
    

    注意

    使用collect方法时要小心!如果收集的 DataFrame 或 RDD 的大小超过了本地驱动程序的内存,Spark 将抛出错误。

  5. 现在,让我们首先将每一行拆分为单词,通过周围的空格进行分割,并合并所有元素,移除大写字母的单词:

    splits = lines.flatMap(lambda x: x.split(‘ ‘))
    lower_splits = splits.map(lambda x: x.lower().strip())
    
  6. 让我们还移除字符串中的thea,以及像‘.’,‘,’这样的标点符号:

    prep = [‘the’, ‘a’, ‘,’, ‘.’]
    
  7. 使用以下命令从我们的标记列表中移除停用词:

    tokens = lower_splits.filter(lambda x: x and x not in prep)
    

    我们现在可以处理我们的标记列表并统计唯一的单词。其思想是生成一个元组列表,其中第一个元素是标记,第二个元素是该标记的计数。

  8. 让我们将我们的标记映射到列表中:

    token_list = tokens.map(lambda x: [x, 1])
    
  9. 使用reduceByKey操作,它会对每个列表应用此操作:

    count = token_list.reduceByKey(add).sortBy(lambda x: x[1], ascending=False)
    count.collect()
    

    输出如下:

图 3.3:解析文本并清理

图 3.3:解析文本并清理

注意

记住,collect()会将所有数据收集到驱动节点!使用tophtop等工具检查是否有足够的内存。

活动 8:从文本中移除停用词

在本活动中,我们将读取一个文本文件,将其拆分为行,并从文本中移除停用词

  1. 读取在练习 8 中使用的文本文件shake.txt

  2. 从文本中提取行并创建一个包含每行的列表。

  3. 将每一行拆分为单词,按空格进行分割,并移除大写字母的单词。

  4. 从我们的标记列表中移除停用词:‘of’,‘a’,‘and’,‘to’。

  5. 处理标记列表并统计唯一单词,生成由标记及其计数组成的元组列表。

  6. 使用reduceByKey操作将我们的标记映射到列表中。

    输出如下:

图 3.4:从文本中移除停用词

图 3.4:从文本中移除停用词

注意

本活动的解决方案可以在第 213 页找到。

我们得到一个元组列表,每个元组包含一个标记和该单词在文本中出现的次数。请注意,在最终对计数进行collect操作(一个动作)之前,作为转换的操作并没有立即执行:我们需要通过count这个动作操作来启动 Spark 执行所有步骤。

其他类型的非结构化数据可以使用前面的示例进行解析,并可以直接操作,如前面的活动所示,或者稍后转换为 DataFrame。

总结

在回顾了大数据的定义之后,我们学习了一些专为存储和处理大数据量而设计的工具。Hadoop 是一个完整的生态系统,包括 HDFS 等工具和框架,旨在在大量廉价计算节点上以分布式方式存储数据,以及资源和作业管理器 YARN。我们看到了如何使用 HDFS fs 命令直接操作 HDFS 上的数据。

我们还学习了关于 Spark 的知识,这是一个非常强大和灵活的并行处理框架,与 Hadoop 集成良好。Spark 拥有不同的 API,如 SQL、GraphX 和 Streaming。我们学习了 Spark 如何使用 DataFrame API 表示数据,以及其计算方式类似于 pandas 的方法。我们还看到了如何使用 Parquet 文件格式高效存储数据,并在分析数据时通过分区来提高性能。最后,我们学习了如何处理诸如文本之类的非结构化数据文件。

在下一章中,我们将深入探讨如何使用更高级的技术和 Spark 进行有意义的统计分析,并学习如何在 Spark 中使用 Jupyter 笔记本。

第五章:第四章

深入了解 Spark

学习目标

到本章结束时,你将能够:

  • 实现基本的 Spark DataFrame API。

  • 从不同数据源读取数据并创建 Spark DataFrame。

  • 使用不同的 Spark DataFrame 选项来操作和处理数据。

  • 使用不同的图表可视化 Spark DataFrame 中的数据。

在本章中,我们将使用 Spark 作为大数据集的分析工具。

介绍

上一章介绍了 Spark,这是一个最受欢迎的分布式数据处理平台,用于处理大数据。

在本章中,我们将学习如何使用 Python API——PySpark来操作 Spark 和 Spark DataFrame。它使我们能够处理 PB 级数据,同时也在实时环境中实现机器学习ML)算法。本章将重点介绍使用 Spark DataFrame 在 PySpark 中的数据处理部分。

注意

在本章中,我们会频繁使用“DataFrame”这个术语。这里指的明确是 Spark 的 DataFrame,除非特别说明。请不要将其与 pandas 的 DataFrame 混淆。

Spark DataFrame 是分布式的数据集合,以命名列的形式组织。它们的灵感来自 R 和 Python 的 DataFrame,并在后台有复杂的优化,使得它们快速、优化且可扩展。

DataFrame API 作为Project Tungsten的一部分开发,旨在提高 Spark 的性能和可扩展性。它首次在 Spark 1.3 中引入。

Spark DataFrame 比其前身 RDD 更容易使用和操作。它们像 RDD 一样是不可变的,并且支持延迟加载,这意味着除非调用动作,否则对 DataFrame 不会执行任何变换。DataFrame 的执行计划由 Spark 本身准备,因此更加优化,使得在 DataFrame 上的操作比在 RDD 上的操作更快。

入门 Spark DataFrame

要开始使用 Spark DataFrame,我们首先需要创建一个名为 SparkContext 的对象。SparkContext 配置了内部服务并促进了来自 Spark 执行环境的命令执行。

注意

我们将使用 Spark 版本 2.1.1,运行在 Python 3.7.1 环境中。Spark 和 Python 安装在一台 MacBook Pro 上,操作系统是 macOS Mojave 10.14.3,配备 2.7 GHz Intel Core i5 处理器和 8 GB 1867 MHz DDR3 RAM。

以下代码片段用于创建SparkContext

from pyspark import SparkContext
sc = SparkContext()

注意

如果你在 PySpark shell 中工作,应该跳过这一步,因为该 shell 在启动时会自动创建sc(SparkContext)变量。不过,在创建 PySpark 脚本或使用 Jupyter Notebook 时,务必创建sc变量,否则代码会抛出错误。

在开始使用 DataFrame 之前,我们还需要创建一个SQLContext。Spark 中的SQLContext是一个类,提供了类似 SQL 的功能。我们可以使用SparkContext来创建SQLContext

from pyspark.sql import SQLContext
sqlc = SQLContext(sc)

在 Spark 中创建 DataFrame 有三种不同的方法:

  • 我们可以以编程方式指定 DataFrame 的模式并手动输入数据。然而,由于 Spark 通常用于处理大数据,除了为小型测试/示例案例创建数据外,这种方法几乎没有其他用途。

  • 创建 DataFrame 的另一种方法是从现有的 Spark RDD 对象中创建。这是有用的,因为在 DataFrame 上工作比直接在 RDD 上工作要容易得多。

  • 我们还可以直接从数据源读取数据以创建 Spark DataFrame。Spark 支持多种外部数据源,包括 CSV、JSON、Parquet、关系型数据库表和 Hive 表。

练习 24:指定 DataFrame 的模式

在本练习中,我们将通过手动指定模式并输入数据来创建一个小的示例 DataFrame。尽管这种方法在实际场景中的应用较少,但它是开始学习 Spark DataFrames 的一个不错的起点:

  1. 导入必要的文件:

    from pyspark import SparkContext
    sc = SparkContext()
    from pyspark.sql import SQLContext
    sqlc = SQLContext(sc)
    
  2. 从 PySpark 模块导入 SQL 工具并指定示例 DataFrame 的模式:

    from pyspark.sql import *
    na_schema = Row("Name","Age")
    
  3. 根据指定模式创建 DataFrame 的行:

    row1 = na_schema("Ankit", 23)
    row2 = na_schema("Tyler", 26)
    row3 = na_schema("Preity", 36)
    
  4. 将行合并在一起创建 DataFrame:

    na_list = [row1, row2, row3]
    df_na = sqlc.createDataFrame(na_list)
    type(df_na)
    
  5. 现在,使用以下命令显示 DataFrame:

    df_na.show()
    

    输出结果如下:

    图 4.1:示例 PySpark DataFrame

图 4.1:示例 PySpark DataFrame

练习 25:从现有 RDD 创建 DataFrame

在本练习中,我们将从现有的 Spark RDD 对象创建一个小的示例 DataFrame:

  1. 创建一个 RDD 对象,我们将把它转换成 DataFrame:

    data = [("Ankit",23),("Tyler",26),("Preity",36)]
    data_rdd = sc.parallelize(data)
    type(data_rdd)
    
  2. 将 RDD 对象转换为 DataFrame:

    data_sd = sqlc.createDataFrame(data_rdd)
    
  3. 现在,使用以下命令显示 DataFrame:

    data_sd.show()
    

    图 4.2:从 RDD 对象转换的 DataFrame

图 4.2:从 RDD 对象转换的 DataFrame

练习 25:使用 CSV 文件创建 DataFrame

可以使用多种不同的数据源来创建 DataFrame。在本练习中,我们将使用开源的 Iris 数据集,该数据集可以在 scikit-learn 库的 datasets 中找到。Iris 数据集是一个多变量数据集,包含 150 条记录,每个品种的 Iris 花(Iris Setosa、Iris Virginica 和 Iris Versicolor)有 50 条记录。

该数据集包含每个 Iris 品种的五个属性,即 花瓣长度花瓣宽度萼片长度萼片宽度品种。我们已将此数据集存储在一个外部 CSV 文件中,我们将其读入 Spark:

  1. 从 Databricks 网站下载并安装 PySpark CSV 阅读器包:

    pyspark –packages com.databricks:spark-csv_2.10:1.4.0
    
  2. 将数据从 CSV 文件读取到 Spark DataFrame 中:

    df = sqlc.read.format('com.databricks.spark.csv').options(header='true', inferschema='true').load('iris.csv')
    type(df)
    
  3. 现在,使用以下命令显示 DataFrame:

    df.show(4)
    

    图 4.3:Iris DataFrame,前四行

图 4.3:Iris DataFrame,前四行

给讲师的备注

激励学生探索其他数据源,如制表符分隔文件、Parquet 文件和关系型数据库等。

从 Spark DataFrame 写入输出

Spark 使我们能够将存储在 Spark DataFrame 中的数据写入本地 pandas DataFrame,或写入 CSV 等外部结构化文件格式。然而,在将 Spark DataFrame 转换为本地 pandas DataFrame 之前,请确保数据能够适应本地驱动程序内存。

在接下来的练习中,我们将探索如何将 Spark DataFrame 转换为 pandas DataFrame。

练习 27: 将 Spark DataFrame 转换为 Pandas DataFrame

在本练习中,我们将使用前一个练习中预创建的 Iris 数据集的 Spark DataFrame,并将其转换为本地 pandas DataFrame。然后我们将把这个 DataFrame 存储到 CSV 文件中。执行以下步骤:

  1. 使用以下命令将 Spark DataFrame 转换为 pandas DataFrame:

    import pandas as pd
    df.toPandas()
    
  2. 现在使用以下命令将 pandas DataFrame 写入 CSV 文件:

    df.toPandas().to_csv('iris.csv')
    

    注意

    将 Spark DataFrame 的内容写入 CSV 文件需要使用 spark-csv 包的一行代码:

    df.write.csv('iris.csv')

探索 Spark DataFrame

Spark DataFrame 相比传统的 RDDs 的一个主要优势是数据的易用性和探索性。数据以更结构化的表格格式存储在 DataFrame 中,因此更容易理解。我们可以计算基本统计信息,如行数和列数,查看模式,并计算摘要统计信息,如均值和标准差。

练习 28: 显示基本的 DataFrame 统计信息

在本练习中,我们将展示数据前几行的基本 DataFrame 统计信息,以及所有数值 DataFrame 列和单个 DataFrame 列的摘要统计信息:

  1. 查看 DataFrame 的模式。模式会以树形结构显示在控制台上:

    df.printSchema()
    

    图 4.4: Iris DataFrame 模式

    图 4.4: Iris DataFrame 模式
  2. 现在,使用以下命令打印 Spark DataFrame 的列名:

    df.schema.names
    

    图 4.5: Iris 列名

    图 4.5: Iris 列名
  3. 要获取 Spark DataFrame 中行数和列数,请使用以下命令:

    ## Counting the number of rows in DataFrame
    df.count()#134
    ## Counting the number of columns in DataFrame
    len(df.columns)#5
    
  4. 让我们获取数据的前 n 行。我们可以使用 head() 方法来实现。然而,我们使用 show() 方法,因为它可以以更好的格式显示数据:

    df.show(4)
    

    输出如下:

    图 4.6: Iris DataFrame,前四行

    图 4.6: Iris DataFrame,前四行
  5. 现在,计算所有数值列的摘要统计信息,如均值和标准差:

    df.describe().show()
    

    输出如下:

    图 4.7: Iris DataFrame,摘要统计

    图 4.7: Iris DataFrame,摘要统计
  6. 要计算 Spark DataFrame 中某一数值列的摘要统计信息,请使用以下命令:

    df.describe('Sepalwidth').show()
    

    输出如下:

    图 4.8: Iris DataFrame,Sepalwidth 列的摘要统计

图 4.8: Iris DataFrame,Sepalwidth 列的摘要统计

活动 9:Spark DataFrames 入门

在本活动中,我们将利用前几部分学习的概念,使用三种方法创建一个 Spark DataFrame。我们还将计算 DataFrame 的统计信息,最后将相同的数据写入 CSV 文件。你可以随意使用任何开源数据集来完成这个活动:

  1. 通过手动指定模式创建一个示例 DataFrame。

  2. 从现有的 RDD 创建一个示例 DataFrame。

  3. 通过从 CSV 文件中读取数据创建一个示例 DataFrame。

  4. 打印第 3 步中读取的示例 DataFrame 的前七行。

  5. 打印第 3 步中读取的示例 DataFrame 的模式。

  6. 打印示例 DataFrame 中的行数和列数。

  7. 打印 DataFrame 的摘要统计信息以及任何两个单独的数值列。

  8. 使用练习中提到的两种方法将示例 DataFrame 的前 7 行写入 CSV 文件。

    注意

    本活动的解决方案可以在第 215 页找到。

使用 Spark DataFrame 进行数据操作

数据操作是任何数据分析的前提。为了从数据中提取有意义的洞察,我们首先需要理解、处理和调整数据。但随着数据量的增加,这一步变得尤为困难。由于数据的规模,即使是简单的操作,如过滤和排序,也会变成复杂的编码问题。Spark DataFrame 使得在大数据上进行数据操作变得轻而易举。

在 Spark DataFrame 中进行数据操作与在常规 pandas DataFrame 中的操作非常相似。大多数 Spark DataFrame 的数据操作都可以通过简单直观的单行代码完成。我们将使用在之前练习中创建的包含鸢尾花数据集的 Spark DataFrame 来进行这些数据操作练习。

练习 29:选择并重命名 DataFrame 中的列

在本练习中,我们将首先使用 withColumnRenamed 方法重命名列,然后使用 select 方法选择并打印模式。

执行以下步骤:

  1. 使用 withColumnRenamed() 方法重命名 Spark DataFrame 的列:

    df = df.withColumnRenamed('Sepal.Width','Sepalwidth')
    

    注意

    Spark 无法识别包含点号(.)的列名。确保使用此方法重命名它们。

  2. 使用 select 方法从 Spark DataFrame 中选择单个列或多个列:

    df.select('Sepalwidth','Sepallength').show(4)
    

    图 4.9:鸢尾花 DataFrame,Sepalwidth 和 Sepallength 列

图 4.9:鸢尾花 DataFrame,Sepalwidth 和 Sepallength 列

练习 30:向 DataFrame 中添加和移除列

在本练习中,我们将使用 withColumn 方法在数据集中添加新列,之后使用 drop 函数将其移除。现在,让我们执行以下步骤:

  1. 使用 withColumn 方法在 Spark DataFrame 中添加一个新列:

    df = df.withColumn('Half_sepal_width', df['Sepalwidth']/2.0)
    
  2. 使用以下命令显示包含新添加列的数据集:

    df.show(4)
    

    图 4.10:引入新列 Half_sepal_width

    图 4.10:引入新列 Half_sepal_width
  3. 现在,要在 Spark 数据框中移除一列,请使用这里说明的 drop 方法:

    df = df.drop('Half_sepal_width')
    
  4. 让我们展示数据集以验证列是否已被移除:

    df.show(4)
    

    图 4.11:移除 Half_sepal_width 列后的鸢尾花数据框

图 4.11:移除 Half_sepal_width 列后的鸢尾花数据框

练习 31:在数据框中显示和计数不同的值

要显示数据框中的不同值,我们使用 distinct().show() 方法。同样,要计数不同的值,我们将使用 distinct().count() 方法。执行以下步骤以打印不同的值及其总数:

  1. 使用 distinct 方法,结合 select 方法,从 Spark 数据框中选择任何列的不同值:

    df.select('Species').distinct().show()
    

    图 4.12:鸢尾花数据框,物种列

    图 4.12:鸢尾花数据框,物种列
  2. 要计算 Spark 数据框中任何列的不同值,请使用 count 方法,并结合 distinct 方法:

    df.select('Species').distinct().count()
    

练习 32:移除重复行和过滤数据框中的行

在这个练习中,我们将学习如何从数据集中移除重复的行,并随后在同一列上执行过滤操作。

执行这些步骤:

  1. 使用 dropDuplicates() 方法从数据框中移除重复值:

    df.select('Species').dropDuplicates().show()
    

    图 4.13:移除重复列后的鸢尾花数据框,物种列

    图 4.13:移除重复列后的鸢尾花数据框,物种列
  2. 使用一个或多个条件从数据框中过滤行。这些多个条件可以通过布尔运算符如 &(与)或 |(或)传递给数据框,类似于我们对 pandas 数据框的操作:

    # Filtering using a single condition
    df.filter(df.Species == 'setosa').show(4)
    

    图 4.14:使用单一条件过滤后的鸢尾花数据框
  3. 现在,要使用多个条件过滤列,请使用以下命令:

    df.filter((df.Sepallength > 5) & (df.Species == 'setosa')).show(4)
    

    图 4.15:使用多个条件过滤后的鸢尾花数据框

图 4.15:使用多个条件过滤后的鸢尾花数据框

练习 33:对数据框中的行进行排序

在这个练习中,我们将探索如何按升序和降序对数据框中的行进行排序。让我们执行以下步骤:

  1. 使用一个或多个条件按升序或降序排序数据框中的行:

    df.orderBy(df.Sepallength).show(5)
    

    图 4.16:过滤后的鸢尾花数据框

    图 4.16:过滤后的鸢尾花数据框
  2. 要按降序排序行,请使用以下命令:

    df.orderBy(df.Sepallength.desc()).show(5)
    

    图 4.17:按降序排序后的鸢尾花数据框

图 4.17:按降序排序后的鸢尾花数据框

练习 34:在数据框中聚合值

我们可以通过一个或多个变量对 DataFrame 中的值进行分组,并计算汇总指标,如 meansumcount 等等。在这个练习中,我们将计算鸢尾花数据集中每个花卉物种的平均花萼宽度。我们还将计算每个物种的行数:

  1. 使用以下命令计算每个物种的平均花萼宽度:

    df.groupby('Species').agg({'Sepalwidth' : 'mean'}).show()
    

    图 4.18:鸢尾花 DataFrame,计算平均花萼宽度
  2. 现在,让我们使用以下命令计算每个物种的行数:

    df.groupby('Species').count().show()
    

    图 4.19:鸢尾花 DataFrame,计算每个物种的行数

图 4.19:鸢尾花 DataFrame,计算每个物种的行数

注意

.agg function; however, the method we used is more popular.

活动 10:使用 Spark DataFrame 进行数据操作

在本活动中,我们将使用之前部分中学到的概念,操作使用鸢尾花数据集创建的 Spark DataFrame。我们将执行基本的数据操作步骤,测试我们在 Spark DataFrame 中处理数据的能力。你可以自由使用任何开源数据集进行此活动。确保所用数据集包含数值变量和类别变量:

  1. 重命名 DataFrame 中的五列。如果 DataFrame 中有更多列,则重命名所有列。

  2. 从 DataFrame 中选择两列数值型列和一列类别型列。

  3. 计算类别变量中不同类别的数量。

  4. 在 DataFrame 中创建两个新列,分别通过将两列数值型列相加和相乘得到。

  5. 删除两个原始数值列。

  6. 按照类别列对数据进行排序。

  7. 计算每个类别变量中每个不同类别的求和列的平均值。

  8. 过滤出花萼宽度大于步骤 7 中计算出的所有平均值的行。

  9. 对结果 DataFrame 进行去重,确保它只包含唯一记录。

    注意

    本活动的解决方案可以在第 219 页找到。

Spark 中的图表

有效可视化数据的能力至关重要。数据的可视化帮助用户更好地理解数据,并发现文本形式中可能忽略的趋势。在 Python 中,有许多类型的图表,每种图表都有其特定的使用场景。

我们将探索一些图表,包括条形图、密度图、箱线图和线性图,用于 Spark DataFrame,使用流行的 Python 绘图包 Matplotlib 和 Seaborn。需要注意的是,Spark 处理的是大数据。因此,在绘制图表之前,请确保数据大小足够合理(即能容纳在计算机的内存中)。这可以通过过滤、汇总或抽样数据来实现。

我们使用的是鸢尾花数据集,它较小,因此我们不需要进行任何预处理步骤来减少数据大小。

教师说明

用户应在开发环境中预先安装并加载 Matplotlib 和 Seaborn 包,然后再开始本节的练习。如果您不熟悉如何安装和加载这些包,请访问 Matplotlib 和 Seaborn 的官方网站。

练习 35:创建条形图

在本练习中,我们将尝试通过条形图绘制每种物种的记录数量。我们需要首先聚合数据并计算每种物种的记录数。然后,我们可以将聚合后的数据转换为常规的 pandas DataFrame,并使用 Matplotlib 和 Seaborn 包创建我们需要的任何类型的图表:

  1. 首先,计算每种花卉物种的行数,并将结果转换为 pandas DataFrame:

    data = df.groupby('Species').count().toPandas()
    
  2. 现在,从结果的 pandas DataFrame 创建一个条形图:

    import seaborn as sns
    import matplotlib.pyplot as plt
    sns.barplot( x = data['Species'], y = data['count'])
    plt.xlabel('Species')
    plt.ylabel('count')
    plt.title('Number of rows per species')
    

    绘制的图形如下:

    图 4.20:计算每个花卉物种的行数后,Iris DataFrame 的条形图

图 4.20:计算每个花卉物种的行数后,Iris DataFrame 的条形图

练习 36:创建线性模型图

在本练习中,我们将绘制两个不同变量的数据点,并在其上拟合一条直线。这类似于在两个变量上拟合一个线性模型,并有助于识别这两个变量之间的相关性:

  1. 从 pandas DataFrame 创建一个data对象:

    data = df.toPandas()
    sns.lmplot(x = "Sepallength", y = "Sepalwidth", data = data)
    
  2. 使用以下命令绘制 DataFrame:

    plt.show()
    

    图 4.21:Iris DataFrame 的线性模型图

图 4.21:Iris DataFrame 的线性模型图

练习 37:创建 KDE 图和箱线图

在本练习中,我们将创建一个核密度估计KDE)图,并接着绘制一个箱线图。请按照以下步骤操作:

  1. 首先,绘制一个 KDE 图,展示变量的分布情况。确保它能帮助我们了解变量的偏斜度和峰度:

    import seaborn as sns
    data = df.toPandas()
    sns.kdeplot(data.Sepalwidth, shade = True)
    plt.show()
    

    图 4.22:Iris DataFrame 的 KDE 图

    图 4.22:Iris DataFrame 的 KDE 图
  2. 现在,使用以下命令绘制 Iris 数据集的箱线图:

    sns.boxplot(x = "Sepallength", y = "Sepalwidth", data = data)
    plt.show()
    

    图 4.23:Iris DataFrame 的箱线图

图 4.23:Iris DataFrame 的箱线图

箱线图是查看数据分布并定位异常值的好方法。它们通过 1st 四分位数、中位数、3rd 四分位数和四分位间距(25%到 75%的百分位数)来表示数据分布。

活动 11:Spark 中的图表

在此活动中,我们将使用 Python 的绘图库,通过不同类型的图表来可视化探索数据。我们使用的是 Kaggle 上的mtcars数据集(www.kaggle.com/ruiromanini/mtcars):

  1. 在 Jupyter Notebook 中导入所有必需的包和库。

  2. mtcars数据集将数据读取到 Spark 对象中。

  3. 使用直方图可视化数据集中任意连续数值变量的离散频率分布:图 4.24:Iris 数据框的直方图

    图 4.24:Iris 数据框的直方图
  4. 使用饼图可视化数据集中各类别的百分比份额:图 4.25:Iris 数据框的饼图

    图 4.25:Iris 数据框的饼图
  5. 使用箱型图绘制连续变量在类别变量各类别下的分布:图 4.26:Iris 数据框的箱型图

    图 4.26:Iris 数据框的箱型图
  6. 使用线性图表可视化连续数值变量的值:图 4.27:Iris 数据框的线性图表

    图 4.27:Iris 数据框的线性图表
  7. 在同一条线性图表中绘制多个连续数值变量的值:

图 4.28:Iris 数据框中绘制多个连续数值变量的线性图表

图 4.28:Iris 数据框中绘制多个连续数值变量的线性图表

注意

本活动的解决方案可以在第 224 页找到。

小结

在本章中,我们介绍了 Spark 数据框的基本概念,并探讨了它们为何优于 RDD。我们探索了创建 Spark 数据框的不同方法,并将 Spark 数据框的内容写入常规的 pandas 数据框和输出文件。

我们在 PySpark 中尝试了实际的数据探索,通过计算 Spark 数据框的基本统计和指标。我们在 Spark 数据框中操作数据,执行数据处理操作,如过滤、选择和聚合。我们还尝试通过绘制数据生成有意义的可视化图表。

此外,我们通过实践操作和活动巩固了对各种概念的理解。

在下一章中,我们将探讨如何处理缺失值以及如何计算 PySpark 中变量之间的相关性。

第六章:第五章

处理缺失值和相关性分析

学习目标

到本章结束时,你将能够:

  • 使用 PySpark 检测和处理数据中的缺失值

  • 描述变量之间的相关性

  • 计算 PySpark 中两个或多个变量之间的相关性

  • 使用 PySpark 创建相关矩阵

在本章中,我们将使用 Iris 数据集处理缺失数据并找到数据值之间的相关性。

介绍

在上一章中,我们学习了 Spark DataFrame 的基本概念,并了解了如何利用它们进行大数据分析。

在本章中,我们将进一步学习如何处理数据中的缺失值和进行相关性分析,这些概念将帮助我们为机器学习和探索性数据分析准备数据。

我们将简要介绍这些概念,以便为读者提供一些背景,但我们将重点介绍如何在 Spark DataFrame 中实现它们。我们将使用上一章中使用的相同 Iris 数据集进行本章的练习。但由于 Iris 数据集没有缺失值,我们随机从原始数据集中删除了Sepallength列中的两个条目和Petallength列中的一个条目。因此,现在我们有了一个包含缺失值的数据集,我们将学习如何使用 PySpark 处理这些缺失值。

我们还将通过计算相关系数和相关矩阵来查看 Iris 数据集中变量之间的相关性。

设置 Jupyter Notebook

开始练习之前,需要执行以下步骤:

  1. 在 Jupyter Notebook 中导入所有必要的模块和包:

    import findspark
    findspark.init()
    import pyspark
    import random
    
  2. 现在,使用以下命令设置SparkContext

    from pyspark import SparkContext
    sc = SparkContext()
    
  3. 同样,使用以下命令在 Jupyter Notebook 中设置SQLContext

    from pyspark.sql import SQLContext
    sqlc = SQLContext(sc)
    

    注意

    在执行下一个命令之前,确保已经从 Databricks 网站(databricks.com/)安装并准备好 PySpark CSV 读取器包。如果没有,请使用以下命令下载:

    pyspark –packages com.databricks:spark-csv_2.10:1.4.0

  4. 将 Iris 数据集从 CSV 文件读取到 Spark DataFrame 中:

    df = sqlc.read.format('com.databricks.spark.csv').options(header = 'true', inferschema = 'true').load('/Users/iris.csv')
    

    上述命令的输出如下:

    df.show(5)
    

    图 5.1:Iris DataFrame

图 5.1:Iris DataFrame

缺失值

没有分配值的数据项称为缺失值。在实际应用中,数据中遇到缺失值是很常见的。缺失值可能由多种原因造成,例如系统/响应者无响应、数据损坏或部分删除。

某些字段比其他字段更容易包含缺失值。例如,来自调查的收入数据可能包含缺失值,因为人们不愿透露自己的收入。

然而,这仍然是困扰数据分析领域的主要问题之一。根据缺失数据的比例,缺失值可能会成为数据准备和探索性分析中的重大挑战。因此,在开始数据分析之前,计算缺失数据的百分比是非常重要的。

在接下来的练习中,我们将学习如何检测并计算 PySpark DataFrame 中缺失值的数量。

练习 38:统计 DataFrame 中的缺失值

在这个练习中,我们将学习如何统计 PySpark DataFrame 列中的缺失值:

  1. 使用以下命令检查 Spark DataFrame 是否存在缺失值:

    from pyspark.sql.functions import isnan, when, count, col
    df.select([count(when(isnan(c) | col(c).isNull(),
                    c)).alias(c) for c in df.columns]).show()
    
  2. 现在,我们将统计加载到 PySpark DataFrame df 对象中的鸢尾花数据集 Sepallength 列中的缺失值:

    df.filter(col('Sepallength').isNull()).count()
    

    输出结果如下:

    2
    

练习 39:统计 DataFrame 所有列中的缺失值

在这个练习中,我们将统计 PySpark DataFrame 所有列中的缺失值:

  1. 首先,导入所有必需的模块,如下所示:

    from pyspark.sql.functions import isnan, when, count, col
    
  2. 现在,使用以下命令显示数据:

    df.select([count(when(isnan(i) | col(i).isNull(), i)).alias(i) for i in df.columns]).show()
    

    输出结果如下:

    图 5.2:鸢尾花 DataFrame,统计缺失值

    输出结果显示,Seapllength 列中有 2 个缺失值,而 Petallength 列中有 1 个缺失值,出现在 PySpark DataFrame 中。

  3. 一种简单的方法是使用 describe() 函数,它提供每一列非缺失值的数量,并给出一些其他汇总统计数据。我们在笔记本中执行以下命令:

    df.describe().show(1)
    

    图 5.3:鸢尾花 DataFrame,使用不同方法统计缺失值

图 5.3:鸢尾花 DataFrame,使用不同方法统计缺失值

如我们所见,Sepallength 列中有 148 个非缺失值,表示有 2 个缺失值,而 Petallength 列中有 149 个非缺失值,表示有 1 个缺失值。

在接下来的部分,我们将探讨如何从 DataFrame 中查找缺失值。

从 DataFrame 中获取缺失值记录

我们还可以使用以下代码过滤掉 PySpark DataFrame 中包含缺失值记录的行:

df.where(col('Sepallength').isNull()).show()

图 5.4:鸢尾花 DataFrame,获取缺失值

show 函数显示 PySpark DataFrame 中的前 20 条记录。由于 Sepallength 列只有两条缺失值记录,所以我们这里只看到两条。

处理 Spark DataFrame 中的缺失值

处理缺失值是数据科学中一个复杂的领域。根据缺失数据的类型和具体业务场景,存在多种技术用于处理缺失值。

这些方法包括从基于简单逻辑的方法到先进的统计方法,如回归和 KNN。然而,无论采用何种方法来处理缺失值,最终我们都会对缺失值数据执行以下两种操作之一:

  • 从数据中删除包含缺失值的记录

  • 用某个常数值填充缺失的条目

在本节中,我们将探索如何使用 PySpark DataFrame 执行这两个操作。

练习 40:从 DataFrame 中删除包含缺失值的记录

在本练习中,我们将删除包含缺失值条目的 PySpark DataFrame 记录。请执行以下步骤:

  1. 要从特定列中删除缺失值,可以使用以下命令:

    df.select('Sepallength').dropna().count()
    

    上述代码将返回 148 作为输出,因为包含缺失值条目的两条 Sepallength 列记录已经从 PySpark DataFrame 中删除。

  2. 要删除 PySpark DataFrame 中包含任何列的缺失值条目的所有记录,请使用以下命令:

    df.dropna().count()
    

DataFrame 中有 3 条记录缺失了值,正如我们在 练习 2:计算所有 DataFrame 列中缺失的值 中看到的那样——其中有两条记录在 Sepallength 列中缺失值,另外一条记录在 Petallength 列中缺失值。

上述代码删除了所有三条记录,从而在 PySpark DataFrame 中返回了 147 条完整的记录。

练习 41:用常数填充 DataFrame 列中的缺失值

在本练习中,我们将把 PySpark DataFrame 列的缺失值条目替换为常数数值。

我们的 DataFrame 中有两个列包含缺失值——SepallengthPetallength

  1. 现在,让我们将这两列中的缺失值条目都替换为常数数值 1

    y = df.select('Sepallength','Petallength').fillna(1)
    
  2. 现在,让我们统计刚刚创建的新 DataFrame y 中的缺失值。这个新 DataFrame 应该没有缺失值:

    y.select([count(when(isnan(i) | col(i).isNull(), i)).alias(i) for i in y.columns]).show()
    

    输出结果如下:

    图 5.5:Iris DataFrame,查找缺失值

    图 5.5:Iris DataFrame,查找缺失值

    有时,我们希望用单一的常数值替换 DataFrame 中所有的缺失值。

  3. 使用以下命令将 PySpark DataFrame 中所有缺失的值替换为常数数值 1:

    z = df.fillna(1)
    
  4. 现在,让我们统计刚刚创建的新 DataFrame z 中的缺失值。这个新 DataFrame 应该没有缺失值:

    z.select([count(when(isnan(k) | col(k).isNull(), k)).alias(k) for k in z.columns]).show()
    

    输出结果如下:

    图 5.6:Iris DataFrame,打印缺失值

图 5.6:Iris DataFrame,打印缺失值

相关性

相关性是衡量两个数值变量之间关联程度的统计量。它给我们一个关于两个变量彼此关系密切程度的概念。例如,年龄和收入是非常相关的变量。观察发现,在一定的阈值范围内,平均收入会随着年龄的增长而增加。因此,我们可以假设年龄和收入之间是正相关的。

注意

然而,相关性并不建立因果关系。因果关系意味着一个变量正在引起另一个变量的变化。

用来计算这种关联的最常见指标是皮尔逊积矩相关系数,通常称为皮尔逊相关系数或简写为相关系数。它以其发明者卡尔·皮尔逊的名字命名。

皮尔逊相关系数通过将两个变量的协方差除以它们标准差的乘积来计算。相关值介于-1+1之间,接近1-1的值表示强关联,接近0的值表示弱关联。系数的符号(+-)告诉我们关联是正相关(两个变量一起增加/减少)还是负相关(反之亦然)。

注意

相关性仅捕捉变量之间的线性关联。因此,如果关联是非线性的,相关系数将无法捕捉到它。两个不相关的变量将具有较低或零的相关系数,但零/低相关值的变量不一定是完全不相关的。

相关性在统计分析中具有重要意义,因为它帮助解释数据,有时还突出变量之间的预测关系。在这一部分,我们将学习如何计算变量之间的相关性,并在 PySpark 中计算相关矩阵。

练习 42:计算相关性

在这个练习中,我们将计算两个数值变量之间的皮尔逊相关系数以及我们 PySpark DataFrame 中所有数值列的相关矩阵。相关矩阵帮助我们可视化所有数值列之间的相关性:

  1. 按照以下步骤计算两个变量之间的相关性:

    df.corr('Sepallength', 'Sepalwidth')
    

    前面的代码输出了这两个变量之间的皮尔逊相关系数-0.1122503554120474

  2. 如下所示,导入相关模块:

    from pyspark.mllib.stat import Statistics
    import pandas as pd
    
  3. 使用以下命令删除数据中的任何缺失值:

    z = df.fillna(1)
    
  4. 在计算相关矩阵之前,使用以下命令去除任何非数值列:

    a = z.drop('Species')
    
  5. 现在,让我们使用以下命令计算相关矩阵:

    features = a.rdd.map(lambda row: row[0:])
    correlation_matrix = Statistics.corr(features, method="pearson")
    
  6. 要将矩阵转换为 pandas DataFrame 以便于可视化,执行以下命令:

    correlation_df = pd.DataFrame(correlation_matrix)
    
  7. 使用原始 PySpark DataFrame 中列的名称重命名 pandas DataFrame 的索引:

    correlation_df.index, correlation_df.columns = a.columns, a.columns
    
  8. 现在,使用以下命令可视化 pandas DataFrame:

    correlation_df
    

    图 5.7:鸢尾花数据框,计算相关性

图 5.7:鸢尾花数据框,计算相关性

活动 12:缺失值处理和使用 PySpark 数据框的相关性分析

在本活动中,我们将检测和处理鸢尾花数据集中的缺失值。我们还将计算相关矩阵,并通过将变量绘制在一起并在图表上拟合一条线性线来验证显示强相关性的变量:

  1. 在 Jupyter 笔记本中执行导入包和库的初始程序。

  2. 设置SparkContextSQLContext

  3. 从 CSV 文件读取数据到 Spark 对象中:图 5.8:鸢尾花数据框,读取数据

    图 5.8:鸢尾花数据框,读取数据
  4. Sepallength列的列均值填充缺失值。

  5. 计算数据集的相关矩阵。确保导入所需的模块。

  6. 从 PySpark 数据框中移除String列,并计算 Spark 数据框中的相关矩阵。

  7. 将相关矩阵转换为 pandas 数据框:图 5.9:鸢尾花数据框,将相关矩阵转换为 pandas 数据框

    图 5.9:鸢尾花数据框,将相关矩阵转换为 pandas 数据框
  8. 加载所需的模块并绘制数据,绘制显示强正相关的变量对,并在其上拟合一条线性线。

    这是x = "Sepallength", y = "Petalwidth"的图表:

    图 5.10:鸢尾花数据框,绘制图表,x = “Sepallength”,y = “Petalwidth”

图 5.10:鸢尾花数据框,绘制图表,x = "Sepallength",y = "Petalwidth"

这是x = "Sepallength", y = "Petalwidth"的图表:

图 5.11:鸢尾花数据框,绘制图表,x = “Sepallength”,y = “Petalwidth”

图 5.11:鸢尾花数据框,绘制图表,x = "Sepallength",y = "Petalwidth"

这是x = "Petallength", y = "Petalwidth"的图表:

图 5.12:鸢尾花数据框,绘制图表,x = “Petallength”,y = “Petalwidth”

图 5.12:鸢尾花数据框,绘制图表,x = "Petallength",y = "Petalwidth"

注意

或者,你可以使用任何数据集进行此活动。

此活动的解决方案可以在第 229 页找到。

总结

本章中,我们学习了如何在 PySpark 数据框中检测和处理缺失值。我们研究了如何进行相关性分析,并量化皮尔逊相关系数的指标。随后,我们计算了不同数值变量对的皮尔逊相关系数,并学习了如何计算 PySpark 数据框中所有变量的相关矩阵。

在下一章中,我们将学习什么是问题定义,并理解如何进行 KPI 生成。我们还将使用数据聚合和数据合并操作(在前面的章节中学习过)并使用图表分析数据。

第七章:第六章

探索性数据分析

学习目标

本章结束时,您将能够:

  • 使用 Jupyter 笔记本实现可重复性概念

  • 以可重复的方式进行数据收集

  • 实施适当的代码实践和标准,以保持分析的可重复性

  • 通过使用 IPython 脚本避免工作重复

本章将学习什么是问题定义,以及如何使用 KPI 分析技术来实现数据的连贯和全面分析。

介绍

数据科学项目中最重要的阶段之一,也是最初的一步,是理解和定义商业问题。然而,这不能仅仅是对现有问题的简单重复陈述或书面报告。为了详细调查商业问题并定义其范围,我们可以使用现有的商业指标来解释与之相关的模式,或者量化并分析历史数据并生成新指标。这些识别出的指标就是关键绩效指标KPIs),用于衡量当前的问题,并向业务利益相关者传达问题的影响。

本章的重点是理解和定义商业问题,识别与之相关的关键指标,并通过 pandas 及类似库使用这些已识别和生成的 KPI 进行描述性分析。本章还涵盖了如何通过结构化的方法和方法论规划数据科学项目,并最终展示如何使用图形和可视化技术呈现问题。

定义商业问题

数据科学中的商业问题是企业在长期短期内面临的挑战,这些问题可能会阻碍商业目标的实现,成为增长和可持续性的制约因素,而这些问题本可以通过高效的数据驱动决策系统来避免。一些典型的数据科学商业问题包括预测下周消费品需求、优化第三方物流3PL)的物流操作、以及识别保险索赔中的欺诈交易。

数据科学和机器学习并不是可以通过将数据输入到预构建算法中来解决这些商业问题的神奇技术。它们在方法和设计方面复杂,需要创建端到端的分析项目。

当企业需要此类解决方案时,如果没有明确理解最终目标,可能会陷入形成需求差距的境地。构建这一强大基础的第一步是定量定义商业问题,然后根据需求进行范围界定和解决方案实施。

以下是一些常见数据科学应用场景的例子,它们能直观地展示当前行业面临的典型商业问题,这些问题通过数据科学和分析得以解决:

  • 不准确的需求/收入/销售预测

  • 低效的客户转化、流失和保持

  • 借贷行业和保险中的欺诈和定价

  • 无效的客户和供应商/分销商评分

  • 无效的交叉销售/追加销售推荐系统

  • 不可预测的机器故障和维护

  • 通过文本数据进行客户情感/情绪分析

  • 未自动化的重复任务,这些任务需要非结构化数据分析

正如我们所知,近年来,行业在技术和创新的推动下发生了巨大的变化。随着技术不断发展,成功的企业能够适应这些变化,从而产生高度发展和复杂的商业挑战和问题。在如此动态的环境中理解新的业务问题并不是一个简单的过程。尽管每个案例的业务问题和应对方法可能会变化,但这种方法在很大程度上是可以概括的。

以下要点是定义和解决业务问题的广泛步骤,接下来的部分将详细描述每个步骤:

  1. 问题识别

  2. 需求收集

  3. 数据管道和工作流

  4. 确定可衡量的指标

  5. 文档和展示

    注意

    目标变量,或研究变量,在数据集中用作分析业务问题的属性/变量/列,也被称为因变量DV),所有其他被考虑用于分析的属性被称为自变量IVs)。

问题识别

让我们从一个例子开始:一家在其共同基金领域拥有强大客户获取能力的资产管理公司AMC),即能够针对正确的客户并将其引入,正在寻求通过基于数据科学的解决方案提高客户保持率,以改善其高端客户的平均客户收入和钱包份额。

在这里,业务问题是如何从现有客户那里增加收入并提高他们的钱包份额。

问题陈述是“我们如何通过客户保持分析提高平均客户收入并增加高端客户的钱包份额?” 总结问题的陈述将是定义业务问题的第一步。

需求收集

一旦问题被识别,与你的客户进行逐条讨论,客户可以是主题专家SME)或在该问题领域有深厚知识的人。

力求从客户的角度理解问题,并从不同的角度询问问题,理解需求,并总结如何从现有的历史数据中定义问题。

有时,你会发现客户自己并不能很好地理解问题。在这种情况下,你应该与客户合作,制定出一个双方都能接受的问题定义。

数据管道和工作流

在你详细理解了问题后,接下来的阶段是定义并商定用于衡量问题的可量化指标,即与客户达成一致,确定用于进一步分析的指标。长期来看,这将为你节省很多麻烦。

这些指标可以与现有的业务绩效追踪系统相关,或者可以从历史数据中导出新的指标。

当你研究跟踪问题的指标时,识别和量化问题的数据可能来自多个数据源、数据库、遗留系统、实时数据等。参与此工作的数据科学家需要与客户的数据管理团队密切合作,提取并收集所需数据,并将其推送到分析工具中进行进一步分析。因此,必须有一个强大的数据获取管道。获取的数据进一步分析,以识别其重要属性及其随时间变化的情况,从而生成 KPI。这是客户参与的关键阶段,与他们团队的密切合作有助于使工作更加顺利。

确定可衡量的指标

一旦通过数据管道收集了所需的数据,我们就可以开发描述性模型来分析历史数据,并生成业务问题的洞察。描述性模型/分析主要是通过时间趋势分析、数据分布密度分析等方法,了解过去发生了什么。为此,必须研究历史数据中的多个属性,以洞察哪些数据属性与当前问题相关。

如前述案例中所解释的例子,某资产管理公司(AMC)正在寻找解决客户留存问题的方案。我们将研究如何生成 KPI,以便理解留存问题。

为此,需要挖掘历史数据,分析以前投资的客户交易模式,并从中导出 KPI。数据科学家必须根据 KPI 在解释问题变动性方面的相关性和效率来开发这些 KPI,或者在此案例中,即客户留存。

文档编制与展示

最后的步骤是记录已识别的关键绩效指标(KPI)、它们的重要趋势,以及它们如何在长期内影响业务。在前述的客户留存案例中,所有这些指标——关系长度平均交易频率流失率——都可以作为 KPI,并用于定量解释问题。

如果我们观察到流失率的趋势,并假设在过去几个月中呈上升趋势,如果我们用图表表示这一点,客户就可以轻松理解,建立预测流失分析来识别即将流失的客户,以及采取更强有力的留存措施的重要性。

需要向客户展示是否有可能建立一个客户留存系统,为此需要完成 KPIs 的文档化和图形表示。在前面的案例中,已识别的 KPIs 及其变化模式需要进行文档化并呈现给客户。

将业务问题转化为可衡量的指标和探索性数据分析(EDA)

如果有一个具体的业务问题出现,我们需要确定定义该业务问题的关键绩效指标(KPIs),并研究与之相关的数据。在生成与问题相关的 KPIs 之后,下一步将是通过探索性数据分析EDA)方法,分析趋势并量化问题。

探索 KPIs 的方法如下:

  • 数据收集

  • 数据生成分析

  • KPI 可视化

  • 特征重要性

数据收集

分析问题所需的数据是定义业务问题的一部分。然而,从数据中选择的特征会根据业务问题的不同而有所变化。以下是几个例子:

  • 如果是推荐引擎或客户流失分析,我们需要查看历史购买和了解你的客户KYC)数据等其他数据。

  • 如果与需求预测相关,我们需要查看每日销售数据。

需要得出结论,所需的数据会根据问题的不同而变化。

数据生成分析

从可用的数据源中,下一步是识别与已定义问题相关的度量指标。除了数据预处理(有关数据处理的详细信息,请参阅第一章Python 数据科学栈),有时我们需要对数据进行处理,以生成这些度量指标,或者它们可以直接从给定数据中获得。

例如,假设我们正在进行监督分析,如预测性维护问题(使用预测分析来预测在设备或机器故障之前的服务状态问题),其中使用的是传感器或计算机生成的日志数据。尽管日志数据是非结构化的,我们仍然可以识别哪些日志文件解释了机器故障,哪些没有。非结构化数据没有列或行。例如,它可能是 XML 格式或类似格式。计算机生成的日志数据就是一个例子。这样的数据需要转换为列和行,或使其结构化,或者对其进行标签化,即通过将数据转换为行和列来为数据提供列名。

另一个例子是识别客户流失并预测未来可能流失的客户,我们拥有与每次购买相关的交易数据及其特征。在这种情况下,我们需要处理数据并转化当前数据,以识别哪些客户已流失,哪些客户没有,从所有与购买相关的数据中进行筛选。

为了更好地解释这一点,在原始数据中,可能会有每个客户的多个购买记录,包括购买日期、购买数量、价格等。所有与某个客户相关的购买记录需要合并为一行,不论该客户是否已经流失(流失指的是停止使用产品或服务的客户,也称为客户流失),并且包含所有相关信息。

在这里,我们将为客户生成一个 KPI:流失未流失属性,其他所有客户也同样如此。定义业务问题的目标变量是已识别的变量。目标变量也称为响应变量因变量。在本章的练***中,通过流失属性进行捕捉和定义。

KPI 可视化

为了理解 KPI 的趋势和模式,我们需要通过交互式可视化技术来表示它们。我们可以使用不同的方法,如箱线图、时间趋势图、密度图、散点图、饼图和热图。在本章的练***中,我们将进一步学习如何生成目标变量的特征重要性并进行 EDA

特征重要性

一旦确定了目标变量,就需要研究数据中其他属性及其在解释目标变量的变异性方面的重要性。为此,我们使用关联、方差和相关方法来建立其他变量与目标变量之间的关系(解释性独立变量)。

根据研究中变量的类型,可以使用多种特征重要性方法和算法,如皮尔逊相关、卡方检验、基于基尼变量重要性、决策树和 Boruta 等算法。

注意

目标变量研究变量,即用于作为数据集中研究业务问题的属性/变量/列,也被称为因变量DV),而所有其他被考虑用于分析的属性则称为自变量IVs)。

在接下来的练习中,我们将涵盖数据收集和分析——数据(通过合并或结合多个数据源生成的分析数据集)的生成与 KPI 可视化,随后我们将介绍特征重要性是什么。

练习 43:从给定数据中识别目标变量及与业务问题相关的 KPI

让我们以银行领域中的订阅问题为例。我们将使用来自葡萄牙某银行机构的直接营销活动的数据,其中客户在活动后要么开设定期存款(参考:www.canstar.com.au/term-deposits/what-is-a-term-deposit/),要么不开设。每个组织对订阅问题的定义都不同。在大多数情况下,愿意订阅某项服务(在这里是定期存款)的客户具有更高的转化潜力(即,从潜在客户到实际客户的转化)。因此,在这个问题中,订阅指标,也就是历史数据的结果,被视为目标变量或 KPI。

我们将使用描述性分析来探索数据趋势。我们将首先识别并定义目标变量(此处为订阅与未订阅)及相关的 KPI:

  1. 从以下在线资源下载 bank.csv 数据:

  2. 为练习创建一个文件夹(packt_exercises),并将下载的数据保存在其中。

  3. 启动 Jupyter notebook 并按示例导入所有所需的库。然后,使用os.chdir()函数设置工作目录:

    import numpy as np
    import pandas as pd
    import seaborn as sns
    import time
    import re
    import os
    import matplotlib.pyplot as plt
    sns.set(style="ticks")
    os.chdir("/Users/svk/Desktop/packt_exercises")
    
  4. 使用以下代码读取 CSV 并探索数据集:

    df = pd.read_csv('bank.csv', sep=';')
    df.head(5)
    print(df.shape)
    df.head(5)
    df.info()
    df.describe()
    
  5. 执行前述命令后,你将看到类似以下的输出:

    图 6.1: 银行数据框

    在研究目标变量(订阅与未订阅——y)时,重要的是要查看其分布情况。此数据集中的目标变量类型是分类的,或者说是多类的。在这种情况下,它是二元的(是/否)。

    当分布偏向某一类别时,问题被称为变量不平衡。我们可以通过条形图研究目标变量的比例。这可以让我们了解每个类别的数量(在此案例中,了解“no”和“yes”各自的数量)。其中,no 的比例远高于 yes,这就解释了数据中的不平衡。

  6. 让我们执行以下命令,以绘制给定数据的条形图:

    count_number_susbc = df["y"].value_counts()
    sns.barplot(count_number_susbc.index, count_number_susbc.values)
    df['y'].value_counts()
    

    输出结果如下:

    图 6.2: 条形图

    图 6.2: 条形图
  7. 现在,我们将查看每个变量并观察它们的分布趋势。下面的直方图是数据集中'age'列(属性)的示例。直方图/密度图是探索数值型/浮动变量的好方法,类似于条形图。它们也可以用于分类数据变量。在这里,我们将使用直方图展示两个数值型变量(agebalance)的示例,并用条形图展示两个分类变量(educationmonth):

    # histogram for age (using matplotlib)
    plt.hist(df['age'], color = 'grey', edgecolor = 'black',
             bins = int(180/5))
    # histogram for age (using seaborn)
    sns.distplot(df['age'], hist=True, kde=False, 
                 bins=int(180/5), color = 'blue',
                 hist_kws={'edgecolor':'black'})
    

    直方图如下所示:

    图 6.3:年龄的直方图

    图 6.3:年龄的直方图
  8. 要绘制数据集中balance属性的直方图,请使用以下命令:

    # histogram for balance (using matplotlib)
    plt.hist(df['balance'], color = 'grey', edgecolor = 'black',
             bins = int(180/5))
    # histogram for balance (using seaborn)
    sns.distplot(df['balance'], hist=True, kde=False, 
                 bins=int(180/5), color = 'blue',
                 hist_kws={'edgecolor':'black'})
    

    平衡的直方图如下所示:

    图 6.4:平衡的直方图

    图 6.4:平衡的直方图
  9. 现在,使用以下代码,绘制数据集中education属性的条形图:

    # barplot for the variable 'education'
    count_number_susbc = df["education"].value_counts()
    sns.barplot(count_number_susbc.index, count_number_susbc.values)
    df['education'].value_counts()
    

    education属性的条形图如下所示:

    图 6.5:教育的条形图

    图 6.5:教育的条形图
  10. 使用以下命令绘制数据集的month属性的条形图:

    # barplot for the variable 'month'
    count_number_susbc = df["month"].value_counts()
    sns.barplot(count_number_susbc.index, count_number_susbc.values)
    df['education'].value_counts()
    

    绘制的图形如下所示:

    图 6.6:月度的条形图

    图 6.6:月度的条形图
  11. 下一个任务是为目标变量的每个类别生成分布并比较分布。绘制目标变量的age属性的直方图(yes/no):

    # generate separate list for each subscription type for age
    x1 = list(df[df['y'] == 'yes']['age'])
    x2 = list(df[df['y'] == 'no']['age'])
    # assign colors for each subscription type 
    colors = ['#E69F00', '#56B4E9']
    names = ['yes', 'no']
    # plot the histogram
    plt.hist([x1, x2], bins = int(180/15), density=True,
             color = colors, label=names)
    # plot formatting
    plt.legend()
    plt.xlabel('IV')
    plt.ylabel('prob distr (IV) for yes and no')
    plt.title('Histogram for Yes and No Events w.r.t. IV')
    

    目标变量的month属性条形图如下所示:

    图 6.7:目标变量的月份属性条形图

    图 6.7:目标变量的月份属性条形图
  12. 现在,使用以下命令,绘制按月分组的目标变量的条形图:

    df.groupby(["month", "y"]).size().unstack().plot(kind='bar', stacked=True, figsize=(20,10))
    

    绘制的图形如下所示:

图 6.8:按月分组的直方图

图 6.8:按月分组的直方图

在本练习中,我们研究了建立 KPI 和目标变量—数据收集分析数据(通过合并或结合多个数据源生成的数据集,用于分析)生成。KPI 和目标变量已经确定—KPI 可视化。现在,在下一个练习中,我们将识别哪些变量在解释目标变量的方差方面是重要的—特征重要性

练习 44:生成目标变量的特征重要性并进行 EDA

在前面的练习中,我们研究了属性的趋势,识别它们的分布,以及如何使用各种图表和可视化方法来进行这些分析。在处理建模问题之前,无论是预测问题还是分类问题(例如,从先前的营销活动数据中,如何预测未来最有可能转化的客户),我们必须对数据进行预处理,并选择那些对订阅活动输出模型有影响的重要特征。为此,我们需要查看属性与结果(目标变量)之间的关联,即每个变量解释目标变量的变异性程度。

变量之间的关联可以通过多种方法绘制;然而,在选择方法/算法时,我们必须考虑数据类型。例如,如果我们研究的是数值型变量(有序的整数、浮动数值等),我们可以使用相关分析;如果我们研究的是具有多个类别的分类变量,则可以使用卡方方法。然而,也有许多算法可以同时处理这两者,并提供可衡量的结果来比较变量的重要性。

在本练习中,我们将研究如何使用各种方法来识别特征的重要性:

  1. 下载bank.csv文件,并使用以下命令读取数据:

    import numpy as np
    import pandas as pd
    import seaborn as sns
    import time
    import re
    import os
    import matplotlib.pyplot as plt
    sns.set(style="ticks")
    # set the working directory # in the example, the folder 
    # 'packt_exercises' is in the desktop
    os.chdir("/Users/svk/Desktop/packt_exercises")
    # read the downloaded input data (marketing data)
    df = pd.read_csv('bank.csv', sep=';')
    
  2. 使用以下命令开发相关矩阵,以识别变量之间的相关性:

    df['y'].replace(['yes','no'],[1,0],inplace=True)
    df['default'].replace(['yes','no'],[1,0],inplace=True)
    df['housing'].replace(['yes','no'],[1,0],inplace=True)
    df['loan'].replace(['yes','no'],[1,0],inplace=True)
    corr_df = df.corr()
    sns.heatmap(corr_df, xticklabels=corr_df.columns.values, yticklabels=corr_df.columns.values, annot = True, annot_kws={'size':12})
    heat_map=plt.gcf(); heat_map.set_size_inches(10,5)
    plt.xticks(fontsize=10); plt.yticks(fontsize=10); plt.show()
    

    绘制的相关矩阵热图如下:

    图 6.9:相关矩阵

    图 6.9:相关矩阵

    注意

    -1+1,其中接近 0 的值表示无关系,-1 表示一种变量增加时,另一变量减少(反向关系),+1 表示一种变量增加时,另一变量也增加(正向关系)。

    独立变量之间的高相关性(即所有除目标变量之外的变量)可能会导致变量之间的多重共线性,这可能影响预测模型的准确性。

    注意

    如果尚未安装 Boruta,请使用以下命令确保安装:

    pip install boruta --upgrade

  3. 基于 Boruta(一个随机森林包装算法)构建特征重要性输出:

    # import DecisionTreeClassifier from sklearn and 
    # BorutaPy from boruta
    import numpy as np
    from sklearn.ensemble import RandomForestClassifier
    from boruta import BorutaPy
    # transform all categorical data types to integers (hot-encoding)
    for col_name in df.columns:
        if(df[col_name].dtype == 'object'):
            df[col_name]= df[col_name].astype('category')
            df[col_name] = df[col_name].cat.codes
    # generate separate dataframes for IVs and DV (target variable)
    X = df.drop(['y'], axis=1).values
    Y = df['y'].values
    # build RandomForestClassifier, Boruta models and
    # related parameter
    rfc = RandomForestClassifier(n_estimators=200, n_jobs=4, class_weight='balanced', max_depth=6)
    boruta_selector = BorutaPy(rfc, n_estimators='auto', verbose=2)
    n_train = len(X)
    # fit Boruta algorithm
    boruta_selector.fit(X, Y)
    

    输出结果如下:

    图 6.10:拟合 Boruta 算法

    图 6.10:拟合 Boruta 算法
  4. 按照以下方式检查特征的排名:

    feature_df = pd.DataFrame(df.drop(['y'], axis=1).columns.tolist(), columns=['features'])
    feature_df['rank']=boruta_selector.ranking_
    feature_df = feature_df.sort_values('rank', ascending=True).reset_index(drop=True)
    sns.barplot(x='rank',y='features',data=feature_df)
    feature_df
    

    输出结果如下:

图 6.11:Boruta 输出

图 6.11:Boruta 输出

数据科学项目生命周期的结构化方法

开始数据科学项目时,需要有一个稳健的方法来规划项目,考虑到潜在的扩展性、维护性和团队结构。我们已经学习了如何定义一个业务问题并通过可量化的参数加以量化,接下来的阶段是项目计划,涵盖了解决方案的开发到部署一个可用的商业应用程序。

本主题结合了业界一些最佳实践,以结构化方式提供数据科学项目生命周期管理的示例。这种方法是一个理想化的阶段顺序;然而,在实际应用中,顺序可以根据所需解决方案的类型而变化。

通常,一个数据科学项目单个模型的部署需要大约三个月的时间,但这可能增加到六个月,甚至长达一年。定义从数据到部署的过程是缩短部署时间的关键。

数据科学项目生命周期阶段

数据科学项目生命周期的各个阶段如下:

  1. 理解和定义业务问题

  2. 数据访问与发现

  3. 数据工程与预处理

  4. 建模开发与评估

阶段 1:理解并定义业务问题

每个数据科学项目都始于了解业务领域和框定业务问题。在大多数组织中,高级分析和数据科学技术的应用还是一个新兴学科,参与其中的大多数数据科学家对业务领域的理解有限。

为了理解业务问题和领域,需要识别关键利益相关者和主题专家SMEs)。然后,主题专家和数据科学家相互合作,提出初步假设,并确定开发解决方案所需的数据源。这是理解数据科学项目的第一阶段。

一旦我们有了结构清晰的业务问题,并确定了所需的数据和数据源,就可以开始数据发现的下一阶段。第一阶段对建立强大的基础来确定范围和解决方案方法至关重要。

阶段 2:数据访问与发现

这一阶段包括识别数据源,并构建数据管道和数据工作流以获取数据。解决方案的性质和依赖的数据在结构、速度和体积方面可能因问题而异。

在这个阶段,重要的是要确定如何从数据源获取数据。获取数据的方式可以通过直接连接器(使用 Python 中的数据库访问库)、使用提供数据访问的 API、直接从网络来源抓取数据,或者甚至是初步原型开发所提供的 CSV 格式的数据转储。一旦建立了强大的数据管道和数据获取工作流,数据科学家就可以开始探索数据,准备分析数据(通过合并或组合多个数据源以生成一个分析用的数据集)。

阶段 3:数据工程与预处理

数据预处理是指将原始数据转化为机器学习算法可以使用的形式。实际上,这是将数据处理成适合进一步分析的结构,或者将数据转化为可以输入到建模过程中的形式。通常,所需的分析数据可能存储在多个表格、数据库,甚至是外部数据源中。

数据科学家需要从这些数据源中识别出所需的属性,并将现有的数据表合并,以获取分析模型所需的数据。这是一个繁琐且耗时的阶段,数据科学家通常会在开发周期中花费大量时间。

数据预处理包括处理异常值、缺失值填充、特征缩放、将数据映射到高斯(或正态)分布、编码类别数据、离散化等活动。

为了开发强大的机器学习模型,数据必须高效地进行预处理。

注意

Python 有一些库用于数据预处理。scikit-learn 有许多高效的内置方法用于数据预处理。scikit-learn 的文档可以在scikit-learn.org/stable/modules/preprocessing.html找到。

让我们通过以下活动,了解如何使用其中一种预处理技术(例如高斯归一化)来进行数据工程和预处理。

活动 13:对给定数据的数值特征进行高斯分布映射

在将数据输入算法之前,需要进行各种预处理技术来准备数据。

注意

访问此网站,了解各种预处理方法:scikit-learn.org/stable/modules/preprocessing.html

在本次练习中,我们将进行数据归一化,这对线性回归、逻辑回归等许多参数化模型非常重要:

  1. 使用bank.csv文件,并将所有所需的包和库导入到 Jupyter 笔记本中。

  2. 现在,确定 DataFrame 中的数值数据。根据数据类型(如类别、数值(浮动或整数)、日期等)对数据进行分类。让我们对数值数据进行归一化处理。

  3. 进行正态性检验,识别出具有非正态分布的特征。

  4. 绘制特征的概率密度图,以直观分析特征的分布。

  5. 准备功率转换模型,并根据box-coxyeo-johnson方法对识别出的特征进行转换,将它们转换为正态分布。

  6. 对新数据(评估数据)应用相同的生成参数进行转换,应用到训练数据中识别出的特征。

    注意

    在转换完成后,前面的图中显示了多个变量的密度图。图中的特征分布更接近高斯分布。

    本活动的解决方案可以在第 236 页找到。

一旦转换完成,我们再次分析特征的正态性,以查看其效果。你会发现,经过转换后,一些特征的原假设未被拒绝(即分布仍然不是高斯分布),并且几乎所有特征的p值都较高(参考本活动的第 2 点)。一旦生成了转换后的数据,我们将其与数值数据绑定。

第 4 阶段:模型开发

一旦我们获得了清理和预处理过的数据,就可以将其输入到机器学习算法中进行探索性分析或预测分析,或用于其他应用。尽管问题的处理方法可以设计出来,比如分类、关联或回归问题,但需要为数据确定的特定算法也必须被识别出来。例如,如果是分类问题,它可能是决策树、支持向量机,或者是一个具有多层的神经网络。

在建模过程中,数据需要分为测试数据和训练数据。模型在训练数据上进行开发,并在测试数据上评估其性能(准确性/误差)。在选择算法后,需要通过数据科学家调整与之相关的参数,以开发一个稳健的模型。

总结

在本章中,我们学习了如何通过一个明确结构化的方法,从数据科学的角度定义业务问题。我们首先理解了如何处理业务问题,如何从利益相关者和业务专家处收集需求,以及如何通过开发初步假设来定义业务问题。

一旦业务问题通过数据管道和工作流定义完成,我们就开始了解如何对收集到的数据进行分析,以生成 KPI 并进行描述性分析,通过各种可视化技术识别历史数据中的关键趋势和模式。

我们还了解了数据科学项目生命周期的结构,从定义业务问题到各种预处理技术和模型开发。在下一章中,我们将学习如何在 Jupyter notebook 上实现高可复现性的概念,以及它在开发中的重要性。

第八章:第七章

大数据分析中的可重复性

学习目标

本章结束时,您将能够:

  • 使用 Jupyter 笔记本实现可重复性的概念

  • 以可重复的方式进行数据收集

  • 实施适当的编码实践和标准,以保持分析的可重复性

  • 避免 IPython 脚本的重复工作

本章中,我们将探讨可重复性在大数据分析中的重要作用。

介绍

在上一章中,我们通过非常结构化的方法学习了如何从数据科学的角度定义商业问题,其中包括如何识别和理解业务需求,解决方案的方法以及如何构建数据管道并进行分析。

本章将讨论计算工作和研究实践的可重复性,这也是今天业界和学术界面临的主要挑战,尤其是在数据科学工作中,其中大部分数据、完整的数据集和相关的工作流程无法完全访问。

今天,大多数研究和技术论文的结尾都会提到样本数据的使用方法、简要描述采用的研究方法以及解决方案的理论思路。这些作品大多缺乏详细的计算过程和逐步方法。这对读者来说,几乎没有提供足够的知识来重复进行相同的工作。这就是可重复编码的基本目标——代码的可复现性至关重要。

在笔记本的整体发展中,已出现包括文本元素来详细注释的功能,这有助于改进复制过程。正因如此,Jupyter 作为一种笔记本在数据科学和研究界日益受到关注。

Jupyter 的开发旨在成为开源软件,采用开放标准和服务,支持多种编程语言的交互式计算,包括 Python、Spark 和 R。

使用 Jupyter 笔记本进行可重复性分析

让我们首先学习什么是计算可重复性。如果提供了开发解决方案所用的原始源代码,并且构建相关软件所用的数据能够产生相同的结果,那么研究、解决方案、原型甚至一个简单的算法都可以被称为可重复的。然而,今天科学界在重现同行以前开发的工作方面遇到了一些挑战,主要是由于缺乏文档和理解过程工作流的困难。

缺乏文档的影响可以在各个层面上看到,从理解方法到代码层面。Jupyter 是一种非常适合改善这一过程的工具,有助于更好的可重现性,以及开发代码的重用。这不仅包括理解每一行或代码片段的作用,还包括理解和可视化数据。

注意

Jon Claerbout,被认为是可重现计算研究的奠基人,早在 1990 年代初期,他要求学生们开发可以一键重新生成的研究工作和结果。他认为,完成的工作是需要时间和精力的,应当保持原样,以便后续的工作能够在不遇到困难的情况下重用之前的工作。从宏观角度看,一个经济体的增长在很大程度上由创新的数量决定。早期工作或研究的可重现性促进了整体创新的增长。

现在让我们看看如何使用 Jupyter 笔记本维护有效的计算可重现性。

以下是通过 Jupyter 笔记本在 Python 中实现可重现性的一些广泛方法:

  • 提供商业问题的详细介绍

  • 记录方法和工作流程

  • 解释数据管道

  • 解释依赖关系

  • 使用源代码版本控制

  • 模块化过程

在接下来的章节中,让我们简要地探索并讨论前面提到的主题。

商业问题介绍

使用 Jupyter 笔记本的一个关键优势是,它将文本内容与代码结合,形成一个工作流程。

我们必须从一个对已识别的商业问题的良好介绍开始,并将其总结文档化在 Jupyter 笔记本中,以提供问题的要点。它必须包含一个问题陈述,从数据科学的角度描述已识别的商业问题,最后得出为什么我们需要进行此分析或过程目标是什么。

记录方法和工作流程

在数据科学中,计算工作可能会有很多反复,如例如进行的探索、使用的算法类型以及调整的参数。

一旦方法的变化已最终确定,就需要对这些变化进行文档记录,以避免工作重复。记录方法和工作流程有助于建立流程。在开发过程中,给代码添加注释是必须的,这应当是一个持续的实践,而不是等到结束或结果出来后再添加注释。到过程结束时,你可能已经忘记了细节,这可能导致对所需努力的错误估算。维护 Jupyter 笔记本并进行良好的文档记录有以下几个优点:

  • 跟踪开发工作

  • 具有自解释性的代码,并为每个过程添加注释

  • 更好地理解代码工作流程以及每一步的结果

  • 通过使特定任务的先前代码片段容易找到,避免反复工作

  • 通过理解代码的重复使用来避免重复工作

  • 知识转移的便利性

解释数据管道

用于识别和量化问题的数据可以来自多个数据源、数据库、遗留系统、实时数据源等。参与此过程的数据科学家将与客户的数据管理团队密切合作,提取和收集所需数据,并将其导入分析工具以进行进一步分析,并创建强大的数据管道来获取这些数据。

详细记录数据来源非常重要(在上一章中已讨论),以维护一个数据字典,解释所考虑的变量、为什么要考虑这些变量、我们拥有的数据类型(结构化或非结构化),以及我们所拥有的数据类型;即,我们是否拥有时间序列数据、多变量数据,或是需要从原始数据源(如图像、文本、语音等)进行预处理和生成的数据。

解释依赖关系

依赖关系是工具中可用的包和库。例如,你可以使用 OpenCV(docs.opencv.org/3.0-beta/doc/py_tutorials/py_tutorials.html),这是一个用于图像相关建模的 Python 库,或者你可以使用像 TensorFlow 这样的 API 进行深度学习建模。另一个例子是:如果你在 Python 中使用 Matplotlib(matplotlib.org/)进行可视化,Matplotlib 可以成为依赖关系的一部分。另一方面,依赖关系还可以包括分析所需的硬件和软件规格。你可以通过使用像 Conda 环境这样的工具,从一开始就显式地管理你的依赖关系,列出所有相关的依赖项(包括它们的包/库版本),这些在之前关于 pandas、NumPy 等依赖关系的章节中已经覆盖。

使用源代码版本控制

版本控制是任何涉及代码的计算活动中非常重要的方面。在开发代码时,错误或缺陷是难以避免的。如果有以前版本的代码可用,我们就可以明确定位到缺陷何时被发现、何时解决,以及解决的过程和付出的努力。版本控制使得这一切成为可能。有时,由于可扩展性、性能或其他原因,你可能需要回退到旧版本。通过使用源代码版本控制工具,你可以随时轻松访问以前版本的代码。

过程模块化

避免重复代码是管理重复任务、维护代码和调试的有效做法。为了高效地实现这一目标,你必须对过程进行模块化。

让我们详细理解这一点。假设你执行了一组数据处理过程,在这些过程中,你开发了代码来完成任务。现在,假设你需要在后续部分再次使用相同的代码;你需要再次添加、复制或运行相同的步骤,这就变成了重复的任务。输入数据和变量名称可能会发生变化。为了处理这个问题,你可以将之前的步骤编写成一个函数,应用于数据集或变量,并将所有这些函数保存为一个独立的模块。你可以称它为函数文件(例如,functions.py,一个 Python 文件)。

在接下来的部分中,我们将更详细地讨论这一点,特别是在以可重现的方式收集和构建高效的数据管道方面。

以可重现的方式收集数据

一旦问题被定义,分析任务的第一步是收集数据。数据可以从多个来源提取:数据库、遗留系统、实时数据、外部数据等。数据来源及其如何输入模型需要被记录下来。

让我们理解如何在 Jupyter 笔记本中使用 markdown 和代码块功能。可以通过 Markdown 单元向 Jupyter 笔记本中添加文本。与任何文本编辑器一样,这些文本可以修改为粗体或斜体。要将单元类型更改为 Markdown,可以使用Cell菜单。接下来,我们将探讨如何在 Jupyter 中使用 Markdown 和代码单元中的各种功能。

Markdown 和代码单元中的功能

  • Jupyter 中的 Markdown:要在Jupyter中选择 Markdown 选项,请点击下拉菜单中的WidgetsMarkdown

图 7.1:Jupyter 笔记本中的 Markdown 选项
  • <h1><h2> 标签:

图 7.2:Jupyter 笔记本中的标题级别

图 7.2:Jupyter 笔记本中的标题级别
  • Jupyter 中的文本:要添加文本,保持其原样,我们不需要为其添加任何标签:图 7.3:在 Jupyter 笔记本中使用普通文本
图 7.3:在 Jupyter 笔记本中使用普通文本
  • **) 在文本的开始和结束处,例如,粗体

图 7.4:在 Jupyter 笔记本中使用粗体文本

图 7.4:在 Jupyter 笔记本中使用粗体文本
  • *) 在文本的开始和结束处:

图 7.5:在 Jupyter 笔记本中使用斜体文本

图 7.5:在 Jupyter 笔记本中使用斜体文本
  • Jupyter 中的代码:要让文本以代码形式出现,从下拉菜单中选择Code选项:

图 7.6:Jupyter 笔记本中的代码

图 7.6:Jupyter 笔记本中的代码

在 Markdown 中解释业务问题

简要介绍业务问题,以便理解项目的目标。业务问题定义是对问题陈述的总结,并包括如何通过数据科学算法解决问题的方法:

图 7.7:问题定义的片段

图 7.7:问题定义的片段

提供数据源的详细介绍

需要正确记录数据源,以了解数据许可的可重复性及进一步工作的要求。数据源添加的示例如下:

图 7.8:Jupyter notebook 中的数据源

图 7.8:Jupyter notebook 中的数据源

在 Markdown 中解释数据属性

需要维护一个数据字典,以便理解属性层面上的数据。这可以包括定义属性及其数据类型:

图 7.9:Markdown 中的详细属性

图 7.9:Markdown 中的详细属性

为了在属性层面理解数据,我们可以使用诸如infodescribe之类的函数;然而,pandas_profiling是一个提供大量描述性信息的库,通过一个函数,我们可以提取以下信息:

图 7.10:数据概况报告

图 7.10:数据概况报告

在 DataFrame 层面,即针对整体数据,这包括所有被考虑的列和行:

  • 变量数量

  • 观察数量

  • 总缺失值 (%)

  • 内存中的总大小

  • 内存中的平均记录大小

  • 相关矩阵

  • 示例数据

在属性层面,即针对特定列,规格如下:

  • 唯一计数

  • 唯一值 (%)

  • 缺失值 (%)

  • 缺失值 (n)

  • 无限值 (%)

  • 无限值 (n)

  • 分布的直方图

  • 极值

图 7.11:属性层面的数据概况报告

图 7.11:属性层面的数据概况报告

练习 45:执行数据可重复性

本练习的目的是学习如何开发具有高可重复性的数据理解代码。我们将使用来自此链接的 UCI 银行和营销数据集:raw.githubusercontent.com/TrainingByPackt/Big-Data-Analysis-with-Python/master/Lesson07/Dataset/bank/bank.csv

让我们执行以下步骤以实现数据的可重复性:

  1. 在 notebook 中添加标题并提到业务问题,使用标记法:图 7.12:介绍和业务问题

    图 7.12:介绍和业务问题
  2. 将所需的库导入到 Jupyter notebook 中:

    import numpy as np
    import pandas as pd
    import time
    import re
    import os
    import pandas_profiling
    
  3. 现在设置工作目录,如以下命令所示:

    os.chdir("/Users/svk/Desktop/packt_exercises")
    
  4. 使用 pandas 的read_csv函数导入并读取输入数据集为df

    df = pd.read_csv('bank.csv', sep=';')
    
  5. 现在使用head函数查看数据集的前五行:

    df.head(5)
    

    输出如下:

    图 7.13:CSV 文件中的数据

    图 7.13:CSV 文件中的数据
  6. 在 Jupyter notebook 中添加数据字典数据理解部分:图 7.14:数据字典

    图 7.14:数据字典

    数据理解部分如下:

    图 7.15:数据理解

    图 7.15:数据理解
  7. 要理解数据规范,可以使用 pandas profiling 生成描述性信息:

    pandas_profiling.ProfileReport(df)
    

    输出如下:

图 7.16:与数据相关的规范摘要

图 7.16:与数据相关的规范摘要

教学人员备注:

本练习展示了如何创建 Jupyter notebook,并包括如何为银行营销问题开发一个可重复的 Jupyter notebook。必须包括对商业问题、数据、数据类型、数据来源等的良好介绍。

代码实践和标准

编写符合一套实践和标准的代码对代码的可重复性非常重要,就像在逐步描述中解释流程一样重要。

这适用于任何你可能使用的编码工具,不仅仅是 Jupyter。某些编码实践和标准应严格遵守,接下来将讨论其中的一些内容。

环境文档

在安装过程中,你应该保留一段代码,用于安装必要的软件包和库。以下实践有助于代码的可重复性:

  • 包括所使用的库/软件包的版本。

  • 下载所使用的库/软件包的原始版本,并在新环境中调用软件包进行安装。

  • 通过在脚本中有效实施,自动安装依赖项。

编写带注释的可读代码

代码注释是一个重要的方面。除了 Jupyter 上的 markdown 选项外,我们还必须为每个代码片段添加注释。有时我们对代码进行的修改可能不会立即使用,但稍后的步骤可能需要。比如,我们可以创建一个对象,虽然下一步不需要,但稍后会用到。如果没有注释,可能会让新用户难以理解代码的流程。注释这些细节非常重要。

当我们使用特定的方法时,必须提供使用该方法的理由。例如,假设在进行数据正态分布转换时,你可以使用 box-coxyeo-johnson。如果数据中有负值,你可能更倾向于使用 yeo-johnson,因为它能够处理负值。此时需要进行如下注释:

图 7.17:带理由的注释

图 7.17:带理由的注释

我们还应遵循良好的命名实践来命名我们创建的对象。例如,您可以将原始数据命名为raw_data,模型数据、预处理数据、分析数据等也可以使用相同的命名方式。当创建像模型和方法这样的对象时,也可以采用类似的命名方式,例如,我们可以将幂变换命名为pt

有效的工作流分割

在开发代码时,您需要设计一系列步骤以实现最终结果。每个步骤都可以是一个过程的一部分。例如,读取数据、理解数据、进行各种转换或构建模型。每个步骤都需要清晰地分开,原因有多个;首先是为了提高代码的可读性,便于理解每个阶段是如何进行的,其次是为了明确每个阶段是如何生成结果的。

例如,在这里,我们正在查看两组活动。一组是生成循环以识别需要归一化的列,另一组是使用前一阶段的输出生成不需要归一化的列:

图 7.18:有效的工作流分割

图 7.18:有效的工作流分割

工作流文档

当产品和解决方案被开发时,它们通常是在沙箱环境中开发、监控、部署和测试的。为了确保在新环境中顺利部署,我们必须为技术和非技术用户提供足够的支持文档。工作流文档包括需求和设计文档、产品文档、方法论文档、安装指南、软件用户手册、硬件和软件要求、故障排除管理以及测试文档。这些通常是产品或解决方案开发中所必需的。我们不能仅仅把一堆代码交给客户或用户,让他们自行运行。工作流文档在客户或用户环境中的部署和集成阶段非常重要,尤其是在确保代码可重复性方面。

从高层次来看,数据科学项目文档可以分为两个部分:

  • 产品文档

  • 方法论文档

产品文档提供了关于如何在 UI/UX 中使用每项功能及其应用的信息。产品文档可以进一步细分为:

  • 安装指南

  • 软件设计与用户手册

  • 测试文档

  • 故障排除管理

方法论文档提供了关于所使用的算法、方法、解决方案方式等信息。

练习 46:高可重复性的缺失值预处理

本练习的目的是学习如何开发具有高可重复性的代码,处理缺失值预处理问题。我们将使用来自这个链接的 UCI 银行和营销数据集:raw.githubusercontent.com/TrainingByPackt/Big-Data-Analysis-with-Python/master/Lesson07/Dataset/bank/bank.csv

执行以下步骤以查找缺失值预处理的可重复性:

  1. 在 Jupyter notebook 中导入所需的库和包,如下所示:

    import numpy as np
    import pandas as pd
    import collections
    import random
    
  2. 设置您选择的工作目录,如下所示:

    os.chdir("/Users/svk/Desktop/packt_exercises")
    
  3. back.csv导入数据集到 Spark 对象中,使用read_csv函数,如下所示:

    df = pd.read_csv('bank.csv', sep=';')
    
  4. 现在,使用 head 函数查看数据集的前五行:

    df.head(5)
    

    输出结果如下:

    图 7.19:银行数据集

    图 7.19:银行数据集

    由于数据集没有缺失值,我们需要人为地添加一些缺失值。

  5. 首先,设置循环参数,如下所示:

    replaced = collections.defaultdict(set)
    ix = [(row, col) for row in range(df.shape[0]) for col in range(df.shape[1])]
    random.shuffle(ix)
    to_replace = int(round(.1*len(ix)))
    
  6. 创建一个for循环来生成缺失值:

    for row, col in ix:
        if len(replaced[row]) < df.shape[1] - 1:
            df.iloc[row, col] = np.nan
            to_replace -= 1
            replaced[row].add(col)
            if to_replace == 0:
                break
    
  7. 使用以下命令,通过查看每一列的缺失值,识别数据中的缺失值:

    print(df.isna().sum())
    

    输出结果如下:

    图 7.20:查找缺失值
  8. 定义四分位数范围IQRs)并将其应用于数据集,以识别离群值:

    num = df._get_numeric_data()
    Q1 = num.quantile(0.25)
    Q3 = num.quantile(0.75)
    IQR = Q3 - Q1
    print(num < (Q1 - 1.5 * IQR))
    print(num > (Q3 + 1.5 * IQR))
    

    输出结果如下:

图 7.21:识别离群值

图 7.21:识别离群值

避免重复

我们都知道,代码的重复或冗余并不是一个好的编程习惯。它会导致调试困难,并且代码长度增加。相同代码的不同版本会在某些时候带来理解上的困难,尤其是在确定哪个版本是正确时。对于调试来说,某个位置的改动需要在代码中各处体现。为了避免不良编程习惯,编写和维护高质量的代码,让我们在接下来的章节中学习一些最佳实践。

使用函数和循环来优化代码

一个函数封装了一个任务,该任务需要一组步骤,将一个或多个输入转换为一个或多个输出,而循环用于对相同代码块的不同样本或子集数据执行重复任务。函数可以为单个变量、多个变量、DataFrame,或者多个参数输入集编写。

例如,假设你只需要对 DataFrame 或矩阵中的数值变量进行某种转换。可以为单个变量编写函数,并将其应用于所有数值列,或者可以为整个 DataFrame 编写函数,该函数会识别数值变量集并将其应用于生成输出。一旦编写了一个函数,它可以应用于接下来代码中的任何类似应用。这将减少重复工作。

编写函数时需要考虑以下挑战:

  • 内部参数变化:任务之间的输入参数可能会发生变化,这是一个常见的挑战。为了处理这个问题,你可以在定义函数输入时,提到函数输入中的动态变量或对象。

  • 未来任务计算过程中的变化:编写一个包含内部函数的函数,如果需要捕获任何变化,将不需要进行太多修改。这样,为新任务编写函数将变得容易。

  • 避免在函数中使用循环:如果需要在数据的多个子集上按行进行处理,可以直接在每个循环中应用函数。这样,函数就不会受到对相同数据的重复代码块的限制。

  • 处理数据类型变化:函数中的返回对象对于不同的任务可能是不同的。根据任务的不同,返回对象可以根据需要转换为其他数据类或数据类型。然而,输入数据类或数据类型可能会因任务的不同而变化。为了解决这个问题,你需要清楚地提供注释,以帮助理解函数的输入。

  • 编写优化函数:数组在执行诸如循环或函数等重复任务时非常高效。在 Python 中,使用 NumPy 数组可以对大多数算术操作生成非常高效的数据处理。

开发用于代码/算法重用的库/包

包包或库封装了一组模块。它们在代码可复现性和生成的模块方面非常可靠。每天,全球的开发者和研究人员都会生成成千上万个包/库。你可以参考 Python 项目打包指南中的包开发说明来开发一个新的包(packaging.python.org/tutorials/packaging-projects/)。本教程将为你提供如何上传并公开分发包以及内部使用的相关信息。

活动 14:进行数据归一化

本次活动的目的是应用在之前练习中学到的各种预处理技术,并使用预处理后的数据开发模型。

现在,让我们执行以下步骤:

  1. 导入所需的库并从bank.csv文件读取数据。

  2. 导入数据集并将 CSV 文件读取到 Spark 对象中。

    检查数据的正态性——下一步是确定数据的正态性。

  3. 将数据按数值和类别进行分段,并对数值数据进行分布转换。

  4. 创建一个 for 循环,循环遍历每一列,以进行正态性测试,检测数据的正态分布。

  5. 创建一个幂变换器。幂变换器将把非正态分布的数据转换为正态分布。所开发的模型将用于转换之前识别的非正态列。

  6. 将创建的幂变换器模型应用于非正态数据。

  7. 要开发一个模型,首先将数据拆分为训练集和测试集以进行交叉验证,训练模型,然后在测试数据上进行预测以进行交叉验证。最后,生成一个交叉验证的混淆矩阵。

    注意事项

    本活动的解决方案可以在第 240 页找到。

总结

在本章中,我们从数据科学的角度学习了如何通过结构化的标准和实践来保持代码的可重复性,以避免在使用 Jupyter Notebook 时重复劳动。

我们首先了解了什么是可重复性,以及它如何影响研究和数据科学工作。我们探讨了可以提高代码可重复性的领域,特别是如何在数据可重复性方面保持有效的编码标准。随后,我们研究了重要的编码标准和实践,以通过有效的代码管理来避免重复劳动,方法是通过工作流分段、为所有关键任务开发函数,以及如何从可重用性角度推广编码,创建库和包。

在下一章,我们将学习如何使用目前为止学到的所有功能来生成完整的分析报告。我们还将学习如何使用各种 PySpark 功能进行 SQL 操作,并且如何开发各种可视化图表。

第九章:第八章

创建完整的分析报告

学习目标

本章结束时,您将能够:

  • 从不同源读取 Spark 数据

  • 对 Spark DataFrame 执行 SQL 操作

  • 以一致的方式生成统计度量

  • 使用 Plotly 生成图表

  • 汇总包含所有先前步骤和数据的分析报告

在本章中,我们将使用 Spark 读取数据、聚合数据并提取统计度量。我们还将使用 Pandas 从聚合数据生成图表,并形成分析报告。

介绍

如果您已经在数据行业工作了一段时间,您会理解与不同数据源打交道、分析它们并以可消费的业务报告呈现它们的挑战。在使用 Python 上的 Spark 时,您可能需要从各种数据源读取数据,如平面文件、REST API 中的 JSON 格式等。

在现实世界中,获取正确格式的数据始终是一个挑战,需要进行多个 SQL 操作来收集数据。因此,任何数据科学家都必须知道如何处理不同的文件格式和数据源,进行基本的 SQL 操作,并将其以可消费的格式呈现。

本章提供了读取不同类型数据、对其进行 SQL 操作、进行描述性统计分析并生成完整分析报告的常用方法。我们将从理解如何将不同类型的数据读取到 PySpark 中开始,然后对其生成各种分析和图表。

从不同数据源读取 Spark 数据

Spark 的一个优势是能够从各种数据源读取数据。然而,这种能力并不一致,并且随着每个 Spark 版本的更新而变化。本章的这一部分将解释如何从 CSV 和 JSON 文件中读取数据。

练习 47:使用 PySpark 对象从 CSV 文件读取数据

要读取 CSV 数据,您必须编写spark.read.csv("带有.csv 的文件名")函数。这里,我们读取的是前面章节中使用的银行数据。

注意

这里使用了sep函数。

我们必须确保根据源数据的分隔方式使用正确的sep函数。

现在,让我们执行以下步骤从bank.csv文件读取数据:

  1. 首先,让我们将所需的包导入到 Jupyter 笔记本中:

    import os
    import pandas as pd
    import numpy as np
    import collections
    from sklearn.base import TransformerMixin
    import random
    import pandas_profiling
    
  2. 接下来,导入所有必需的库,如下所示:

    import seaborn as sns
    import time
    import re
    import os
    import matplotlib.pyplot as plt
    

现在,使用tick主题,这将使我们的数据集更加可视化,并提供更高的对比度:

sns.set(style="ticks")
  1. 现在,使用以下命令更改工作目录:

    os.chdir("/Users/svk/Desktop/packt_exercises")
    
  2. 让我们导入构建 Spark 会话所需的库:

    from pyspark.sql import SparkSession
    spark = SparkSession.builder.appName('ml-bank').getOrCreate()
    
  3. 现在,让我们在创建df_csv Spark 对象后,通过以下命令读取 CSV 数据:

    df_csv = spark.read.csv('bank.csv', sep=';', header = True, inferSchema = True)
    
  4. 使用以下命令打印模式:

    df_csv.printSchema()
    

    输出结果如下:

    图 8.1:银行模式

图 8.1:银行模式

使用 PySpark 对象读取 JSON 数据

要读取 JSON 数据,必须在设置 SQL 上下文后使用read.json("带有.json 的文件名")函数:

图 8.2:在 PySpark 中读取 JSON 文件

图 8.2:在 PySpark 中读取 JSON 文件

Spark DataFrame 上的 SQL 操作

Spark 中的 DataFrame 是一个分布式的行列集合。它与关系型数据库中的表或 Excel 工作表相同。Spark 的 RDD/DataFrame 能够高效处理大量数据,并且可以处理 PB 级别的数据,无论是结构化数据还是非结构化数据。

Spark 通过将 DataFrame 组织成列来优化数据查询,这有助于 Spark 理解数据的模式。最常用的一些 SQL 操作包括对数据进行子集化、合并数据、过滤、选择特定列、删除列、删除所有空值以及添加新列等。

练习 48:在 PySpark 中读取数据并进行 SQL 操作

对于数据的总结统计,我们可以使用 spark_df.describe().show()函数,它将提供 DataFrame 中所有列的countmeanstandard deviationmaxmin等信息。

例如,在我们所考虑的数据集——银行营销数据集(raw.githubusercontent.com/TrainingByPackt/Big-Data-Analysis-with-Python/master/Lesson08/bank.csv)——中,可以通过以下方式获得总结统计数据:

  1. 创建一个新的 Jupyter 笔记本后,导入所有必要的包,如下所示:

    import os
    import pandas as pd
    import numpy as np
    
  2. 现在,使用以下命令更改工作目录:

    os.chdir("/Users/svk/Desktop/packt_exercises")
    
  3. 导入所有需要的库以构建 Spark 会话:

    from pyspark.sql import SparkSession
    spark = SparkSession.builder.appName('ml-bank').getOrCreate()
    
  4. 使用 Spark 对象创建并读取 CSV 文件中的数据,如下所示:

    spark_df = spark.read.csv('bank.csv', sep=';', header = True, inferSchema = True)
    
  5. 现在,让我们使用以下命令打印 Spark 对象的前五行:

    spark_df.head(5)
    

    输出结果如下:

    图 8.3:银行数据的前五行(非结构化)

    图 8.3:银行数据的前五行(非结构化)
  6. 前面的输出是非结构化的。让我们首先识别数据类型,以便获取结构化数据。使用以下命令打印每列的数据类型:

    spark_df.printSchema()
    

    输出结果如下:

    图 8.4:银行数据类型(结构化)

    图 8.4:银行数据类型(结构化)
  7. 现在,让我们计算行和列的总数,并查看数据的概况:

    spark_df.count()
    

    输出结果如下:

    4521
    len(spark_df.columns), spark_df.columns
    

    输出结果如下:

    图 8.5:行和列名称的总数

    图 8.5:行和列名称的总数
  8. 使用以下命令打印 DataFrame 的总结统计:

    spark_df.describe().show()
    

    输出结果如下:

    图 8.6:数值列的总结统计

    图 8.6:数值列的总结统计

    要从 DataFrame 中选择多个列,可以使用 spark_df.select('col1', 'col2', 'col3') 函数。例如,使用以下命令从 balancey 列中选择前五行:

    spark_df.select('balance','y').show(5)
    

    输出如下:

    图 8.7:余额和 y 列的数据

    图 8.7:余额和 y 列的数据
  9. 为了识别两个变量之间在频率层次上的关系,可以使用 crosstab。要得出两个列之间的交叉表,可以使用 spark_df.crosstab('col1', 'col2') 函数。交叉表是针对两个分类变量进行的,而不是数字变量:

    spark_df.crosstab('y', 'marital').show()
    

    图 8.8:分类列的配对频率

    图 8.8:分类列的配对频率
  10. 现在,让我们向数据集添加一列新列:

    # sample sets
    sample1 = spark_df.sample(False, 0.2, 42)
    sample2 = spark_df.sample(False, 0.2, 43)
    # train set
    train = spark_df.sample(False, 0.8, 44)
    train.withColumn('balance_new', train.balance /2.0).select('balance','balance_new').show(5)
    

    输出如下:

    图 8.9:新添加列的数据

    图 8.9:新添加列的数据
  11. 使用以下命令删除新创建的列:

    train.drop('balance_new)
    

练习 49:创建和合并两个 DataFrame

在本练习中,我们将提取并使用银行营销数据(archive.ics.uci.edu/ml/datasets/bank+marketing)来自 UCI 机器学习库。目标是使用 PySpark 对 Spark DataFrame 执行合并操作。

数据与葡萄牙银行机构的直接营销活动相关。营销活动主要通过电话进行。通常,需要对同一客户进行多次联系,以判断该产品(银行定期存款)是否会被()或不会被()订阅。

现在,让我们从当前的银行营销数据创建两个 DataFrame,并基于主键将它们合并:

  1. 首先,在 Jupyter notebook 中导入所需的头文件:

    import os
    import pandas as pd
    import numpy as np
    import pyspark
    
  2. 现在,使用以下命令更改工作目录:

    os.chdir("/Users/svk/Desktop/packt_exercises")
    
  3. 导入构建 Spark 会话所需的所有库:

    from pyspark.sql import SparkSession
    spark = SparkSession.builder.appName('ml-bank').getOrCreate()
    
  4. 使用以下命令将数据从 CSV 文件读取到 Spark 对象:

    spark_df = spark.read.csv('bank.csv', sep=';', header = True, inferSchema = True)
    
  5. 打印 Spark 对象的前五行:

    spark_df.head(5)
    

    输出如下:

    图 8.10:前五行的银行数据(非结构化)
  6. 现在,为了使用主键(ID)合并两个 DataFrame,首先需要将其拆分成两个 DataFrame。

  7. 首先,添加一个包含 ID 列的新 DataFrame:

    from pyspark.sql.functions import monotonically_increasing_id
    train_with_id = spark_df.withColumn("ID", monotonically_increasing_id())
    
  8. 然后,创建另一个列 ID2

    train_with_id = train_with_id.withColumn('ID2', train_with_id.ID)
    
  9. 使用以下命令拆分 DataFrame:

    train_with_id1 = train_with_id.drop('balance', "ID2")
    train_with_id2 = train_with_id.select('balance', "ID2")
    
  10. 现在,修改 train_with_id2 的 ID 列名:

    train_with_id2 = train_with_id2.withColumnRenamed("ID2", "ID")
    
  11. 使用以下命令合并 train_with_id1train_with_id2

    train_merged = train_with_id1.join(train_with_id2, on=['ID'], how='left_outer')
    

练习 50:子集化 DataFrame

在本次练习中,我们将从 UCI 机器学习库提取并使用银行营销数据(archive.ics.uci.edu/ml/datasets/bank+marketing)。目标是使用 PySpark 对 Spark 数据框进行过滤/子集操作。

让我们从银行营销数据中提取出余额大于0的子集:

  1. 首先,在 Jupyter 笔记本中导入所需的头文件:

    import os
    import pandas as pd
    import numpy as np
    import pyspark
    
  2. 现在,使用以下命令更改工作目录:

    os.chdir("/Users/svk/Desktop/packt_exercises")
    
  3. 导入构建 Spark 会话所需的所有库:

    from pyspark.sql import SparkSession
    spark = SparkSession.builder.appName('ml-bank').getOrCreate()
    
  4. 现在,使用以下命令将 CSV 数据作为 Spark 对象读取:

    spark_df = spark.read.csv('bank.csv', sep=';', header = True, inferSchema = True)
    
  5. 让我们运行 SQL 查询以子集化并过滤数据框:

    train_subsetted = spark_df.filter(spark_df.balance > 0.0)
    pd.DataFrame(train_subsetted.head(5))
    

    输出如下:

图 8.11:过滤后的数据框

生成统计度量

Python 是一种通用语言,具有统计模块。很多统计分析(如描述性分析,包括识别数值变量的数据分布,生成相关矩阵,识别类别变量的各个层次频率及其众数等)可以在 Python 中完成。以下是一个相关性示例:

图 8.12:分段数值数据与相关矩阵输出

图 8.12:分段数值数据与相关矩阵输出

识别数据分布并对其进行标准化对参数模型(如yeo-johnson方法)进行数据标准化非常重要:

图 8.13:识别数据分布——正态性检验

图 8.13:识别数据分布——正态性检验

然后使用yeo-johnsonbox-cox方法对识别出的变量进行标准化。

生成特征的重要性在数据科学项目中非常重要,尤其是在使用预测技术时。这大致属于统计分析范畴,因为各种统计技术用于识别重要的变量。这里使用的一种方法是Boruta,它是一个围绕RandomForest算法的变量重要性分析方法。为此,我们将使用BorutaPy包:

图 8.14:特征重要性

图 8.14:特征重要性

活动 15:使用 Plotly 生成可视化

在本次活动中,我们将从 UCI 机器学习库提取并使用银行营销数据。目标是使用 Python 中的 Plotly 生成可视化图表。

注意

Plotly 的 Python 绘图库可以生成互动式、出版质量的图表。

执行以下步骤以使用 Plotly 生成可视化图表:

  1. 将所需的库和包导入到 Jupyter 笔记本中。

  2. 导入用于 Plotly 的数据可视化所需的库:

    import plotly.graph_objs as go
    from plotly.plotly import iplot
    import plotly as py
    
  3. bank.csv文件中读取数据到 Spark 数据框。

  4. 检查您系统上运行的 Plotly 版本。确保您正在运行更新版本。使用pip install plotly --upgrade命令,然后运行以下代码:

    from plotly import __version__
    from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
    print(__version__) # requires version >= 1.9.0
    

    输出如下:

    3.7.1
    
  5. 现在导入所需的库以使用 Plotly 绘制图表:

    import plotly.plotly as py
    import plotly.graph_objs as go
    from plotly.plotly import iplot
    init_notebook_mode(connected=True)
    
  6. 在以下命令中设置 Plotly 凭据,如此处所示:

    plotly.tools.set_credentials_file(username='Your_Username', api_key='Your_API_Key')
    

    注意

    要为 Plotly 生成 API 密钥,请注册帐户并转到plot.ly/settings#/。单击API Keys选项,然后单击Regenerate Key选项。

  7. 现在,使用 Plotly 绘制以下每个图形:

    条形图:

    图 8.15: 银行数据的条形图

图 8.15: 银行数据的条形图

散点图:

图 8.16: 银行数据的散点图

图 8.16: 银行数据的散点图

箱线图:

图 8.17: 银行数据的箱线图

图 8.17: 银行数据的箱线图

注意

此活动的解决方案可在第 248 页找到。

总结

在本章中,我们学习了如何将数据从各种来源导入 Spark 环境作为 Spark DataFrame。此外,我们还学习了如何对该 DataFrame 执行各种 SQL 操作,以及如何生成各种统计措施,如相关性分析,数据分布识别,构建特征重要性模型等。我们还研究了如何使用 Plotly 离线生成有效的图表,您可以生成各种图表以开发分析报告。

本书希望为您提供一次关于大数据的激动人心的旅程。我们从 Python 开始,涵盖了 Python 数据科学堆栈的几个库:NumPy 和 Pandas,我们还看到如何使用 Jupyter 笔记本。然后,我们学习了如何创建信息化的数据可视化图表,介绍了良好图表的一些指导原则,并使用 Matplotlib 和 Seaborn 生成图形。接着,我们开始使用大数据工具 - Hadoop 和 Spark,从而理解了基本原理和操作。

我们已经看到如何在 Spark 中使用 DataFrame 来操作数据,并且已经掌握了利用诸如相关性和维度缩减等概念来更好地理解我们的数据。本书还涵盖了可复现性,以便在需要时能够支持和更好地复制分析,我们以最终报告结束了我们的旅程。希望本书涵盖的主题和实际示例能帮助您在数据旅程的各个领域中取得成功。

第十章:附录

关于

本节内容旨在帮助学生完成书中的活动。它包括学生必须执行的详细步骤,以实现活动目标。

第一章:Python 数据科学工具栈

活动 1:IPython 和 Jupyter

  1. 在文本编辑器中打开python_script_student.py文件,将内容复制到 IPython 中的笔记本,并执行操作。

  2. 将 Python 脚本中的代码复制并粘贴到 Jupyter 笔记本中:

    import numpy as np
    def square_plus(x, c):
        return np.power(x, 2) + c
    
  3. 现在,更新xc变量的值。然后,修改函数的定义:

    x = 10
    c = 100
    result = square_plus(x, c)
    print(result)
    

    输出如下:

    200
    

活动 2:处理数据问题

  1. 导入 pandas 和 NumPy 库:

    import pandas as pd
    import numpy as np
    
  2. 从美国环境保护局获取 RadNet 数据集,数据可通过 Socrata 项目下载:

    url = "https://opendata.socrata.com/api/views/cf4r-dfwe/rows.csv?accessType=DOWNLOAD"
    df = pd.read_csv(url)
    
  3. 创建一个包含 RadNet 数据集中放射性同位素的数值列的列表:

    columns = df.columns
    id_cols = ['State', 'Location', "Date Posted", 'Date Collected', 'Sample Type', 'Unit']
    columns = list(set(columns) - set(id_cols))
    columns
    
  4. 对一列使用apply方法,并使用一个lambda函数比较Non-detect字符串:

    df['Cs-134'] = df['Cs-134'].apply(lambda x: np.nan if x == "Non-detect" else x)
    df.head()
    

    输出如下:

    图 1.19:应用 lambda 函数后的 DataFrame

    图 1.19:应用 lambda 函数后的 DataFrame
  5. 将一列中的文本值替换为NaN,使用np.nan

    df.loc[:, columns] = df.loc[:, columns].applymap(lambda x: np.nan if x == 'Non-detect' else x)
    df.loc[:, columns] = df.loc[:, columns].applymap(lambda x: np.nan if x == 'ND' else x)
    
  6. 使用相同的 lambda 比较,并对多个列同时使用applymap方法,使用在第一步中创建的列表:

    df.loc[:, ['State', 'Location', 'Sample Type', 'Unit']] = df.loc[:, ['State', 'Location', 'Sample Type', 'Unit']].applymap(lambda x: x.strip())
    
  7. 创建一个包含所有非数值列的列表:

    df.dtypes
    

    输出如下:

    图 1.20:列及其类型列表

    图 1.20:列及其类型列表
  8. 使用to_numeric函数将 DataFrame 对象转换为浮动数值:

    df['Date Posted'] = pd.to_datetime(df['Date Posted'])
    df['Date Collected'] = pd.to_datetime(df['Date Collected'])
    for col in columns:
        df[col] = pd.to_numeric(df[col])
    df.dtypes
    

    输出如下:

    图 1.21:列及其类型列表

    图 1.21:列及其类型列表
  9. 使用选择和过滤方法,确保字符串列的名称没有任何空格:

    df['Date Posted'] = pd.to_datetime(df['Date Posted'])
    df['Date Collected'] = pd.to_datetime(df['Date Collected'])
    for col in columns:
        df[col] = pd.to_numeric(df[col])
    df.dtypes
    

    输出如下:

图 1.22:应用选择和过滤方法后的 DataFrame

图 1.22:应用选择和过滤方法后的 DataFrame

活动 3:使用 Pandas 绘制数据

  1. 使用我们正在处理的 RadNet DataFrame。

  2. 修复所有数据类型问题,正如我们之前看到的那样。

  3. 创建一个图表,对Location进行筛选,选择San Bernardino市,并且选择一个放射性同位素,x轴设置为datey轴为放射性同位素I-131

    df.loc[df.Location == 'San Bernardino'].plot(x='Date Collected', y='I-131')
    

    输出如下:

    图 1.23:收集日期与 I-131 的关系图
  4. 创建一个散点图,显示两个相关放射性同位素I-131I-132的浓度:

    fig, ax = plt.subplots()
    ax.scatter(x=df['I-131'], y=df['I-132'])
    _ = ax.set(
        xlabel='I-131',
        ylabel='I-132',
        title='Comparison between concentrations of I-131 and I-132'
    )
    

    输出如下:

图 1.24:I-131 和 I-132 浓度图

图 1.24:I-131 和 I-132 浓度图

第二章:使用 Matplotlib 和 Seaborn 进行统计可视化

活动 4:使用面向对象 API 和 Pandas DataFrame 绘制线形图

  1. 在 Jupyter notebook 中导入所需的库,并从 Auto-MPG 数据集仓库读取数据集:

    %matplotlib inline
    import matplotlib as mpl
    import matplotlib.pyplot as plt import numpy as np
    import pandas as pd
    url = "https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data"
    df = pd.read_csv(url)
    
  2. 提供列名以简化数据集,如下所示:

    column_names = ['mpg', 'cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'year', 'origin', 'name']
    
  3. 现在读取包含列名的新数据集并显示:

    df = pd.read_csv(url, names= column_names, delim_whitespace=True)
    df.head()
    

    图形如下:

    图 2.29:auto-mpg 数据框

    图 2.29:auto-mpg 数据框
  4. 使用以下命令将 horsepoweryear 数据类型转换为浮动和整数:

    df.loc[df.horsepower == '?', 'horsepower'] = np.nan
    df['horsepower'] = pd.to_numeric(df['horsepower'])
    df['full_date'] = pd.to_datetime(df.year, format='%y')
    df['year'] = df['full_date'].dt.year
    
  5. 让我们显示数据类型:

    df.dtypes
    

    输出结果如下:

    图 2.30:数据类型

    图 2.30:数据类型
  6. 现在使用以下命令绘制每年平均的 horsepower

    df.groupby('year')['horsepower'].mean().plot()
    

    输出结果如下:

图 2.31:折线图

活动 5:使用散点图理解变量之间的关系

  1. 在 Jupyter notebook 中导入所需的库,并从 Auto-MPG 数据集仓库读取数据集:

    %matplotlib inline
    import pandas as pd
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import seaborn as sns
    url = "https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data"
    df = pd.read_csv(url)
    
  2. 提供列名以简化数据集,如下所示:

    column_names = ['mpg', 'cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'year', 'origin', 'name']
    
  3. 现在读取包含列名的新数据集并显示:

    df = pd.read_csv(url, names= column_names, delim_whitespace=True)
    df.head()
    

    图形如下:

    图 2.32:Auto-mpg 数据框

    图 2.32:Auto-mpg 数据框
  4. 现在使用 scatter 方法绘制散点图:

    fig, ax = plt.subplots()
    ax.scatter(x = df['horsepower'], y=df['weight'])
    

    输出结果如下:

图 2.33:使用 scatter 方法的散点图

图 2.33:使用 scatter 方法的散点图

活动 6:将图形导出为磁盘文件

  1. 在 Jupyter notebook 中导入所需的库,并从 Auto-MPG 数据集仓库读取数据集:

    %matplotlib inline
    import pandas as pd
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import seaborn as sns
    url = "https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data"
    df = pd.read_csv(url)
    
  2. 提供列名以简化数据集,如下所示:

    column_names = ['mpg', 'cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'year', 'origin', 'name']
    
  3. 现在读取包含列名的新数据集并显示:

    df = pd.read_csv(url, names= column_names, delim_whitespace=True)
    
  4. 使用以下命令创建条形图:

    fig, ax = plt.subplots()
    df.weight.plot(kind='hist', ax=ax)
    

    输出结果如下:

    图 2.34:条形图

    图 2.34:条形图
  5. 使用 savefig 函数将其导出为 PNG 文件:

    fig.savefig('weight_hist.png')
    

活动 7:完整的图表设计

  1. 在 Jupyter notebook 中导入所需的库,并从 Auto-MPG 数据集仓库读取数据集:

    %matplotlib inline
    import pandas as pd
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import seaborn as sns
    url = "https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data"
    df = pd.read_csv(url)
    
  2. 提供列名以简化数据集,如下所示:

    column_names = ['mpg', 'cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'year', 'origin', 'name']
    
  3. 现在读取包含列名的新数据集并显示:

    df = pd.read_csv(url, names= column_names, delim_whitespace=True)
    
  4. yearcylinders 进行分组,并取消将它们用作索引的选项:

    df_g = df.groupby(['year', 'cylinders'], as_index=False)
    
  5. 计算每加仑英里数的平均值,并按分组设置 year 为索引:

    df_g = df_g.mpg.mean()
    
  6. year 设置为数据框的索引:

    df_g = df_g.set_index(df_g.year)
    
  7. 使用面向对象的 API 创建图形和坐标轴:

    import matplotlib.pyplot as plt
    fig, axes = plt.subplots()
    
  8. cylindersdf_g 数据集进行分组,并使用创建的大小为 (10, 8) 的坐标轴绘制每加仑英里数变量:

    df = df.convert_objects(convert_numeric=True)
    df_g = df.groupby(['year', 'cylinders'], as_index=False).horsepower.mean()
    df_g = df_g.set_index(df_g.year)
    
  9. 设置 标题x 轴标签和 y 轴标签:

    fig, axes = plt.subplots()
    df_g.groupby('cylinders').horsepower.plot(axes=axes, figsize=(12,10))
    _ = axes.set(
        title="Average car power per year",
        xlabel="Year",
        ylabel="Power (horsepower)"
    
    )
    

    输出结果如下:

    图 2.35:每年平均汽车功率的折线图(无图例)
  10. 包括图例,如下所示:

    axes.legend(title='Cylinders', fancybox=True)
    

    图 2.36:每年平均汽车功率的折线图(有图例)
  11. 将图像保存为 PNG 文件:

    fig.savefig('mpg_cylinder_year.png')
    

第三章:与大数据框架协作

活动 8:解析文本

  1. 使用 text 方法将文本文件读入 Spark 对象:

    rdd_df = spark.read.text("/localdata/myfile.txt").rdd
    

    为了解析我们正在读取的文件,我们将使用 lambda 函数和 Spark 操作,例如 mapflatMapreduceByKeyflatmap 将一个函数应用到 RDD 的所有元素,扁平化结果并返回转换后的 RDD。reduceByKey 会根据给定的键合并值,进行值的合并。借助这些函数,我们可以统计文本中的行数和单词数。

  2. 使用以下命令从文本中提取 lines

    lines = rdd_df.map(lambda line: line[0])
    
  3. 这将把文件中的每一行拆分成列表中的一个条目。要检查结果,可以使用 collect 方法,它会将所有数据收集回驱动程序进程:

    lines.collect()
    
  4. 现在,让我们使用 count 方法统计行数:

    lines.count()
    

    注意

    使用 collect 方法时要小心!如果被收集的 DataFrame 或 RDD 大于本地驱动程序的内存,Spark 会抛出错误。

  5. 现在,让我们首先将每一行按空格分割成单词,并将所有元素合并,去除大写字母的单词:

    splits = lines.flatMap(lambda x: x.split(' '))
    lower_splits = splits.map(lambda x: x.lower())
    
  6. 我们还要移除 停用词。我们本可以使用 NLTK 提供的更一致的停用词列表,但现在我们将自定义一个:

    stop_words = ['of', 'a', 'and', 'to']
    
  7. 使用以下命令从我们的 token 列表中移除停用词:

    tokens = lower_splits.filter(lambda x: x and x not in stop_words)
    

    现在我们可以处理我们的 token 列表并统计唯一单词的数量。这个思路是生成一个元组列表,第一个元素是 token,第二个元素是该特定 token 的 count

  8. 首先,让我们将我们的 token 映射到一个列表:

    token_list = tokens.map(lambda x: [x, 1])
    
  9. 使用 reduceByKey 操作,它将对每个列表应用该操作:

    count = token_list.reduceByKey(add).sortBy(lambda x: x[1], ascending=False)
    count.collect()
    

记住,将所有数据收集回驱动节点!始终使用如 tophtop 等工具检查是否有足够的内存。

第四章:深入探索 Spark

活动 9:Spark DataFrame 入门

如果你正在使用 Google Collab 运行 Jupyter notebook,请添加以下几行代码以确保你已经设置好环境:

!apt-get install openjdk-8-jdk-headless -qq > /dev/null
!wget -q http://www-us.apache.org/dist/spark/spark-2.4.0/spark-2.4.0-bin-hadoop2.7.tgz
!tar xf spark-2.4.0-bin-hadoop2.7.tgz
!pip install -q findspark
import os
os.environ["JAVA_HOME"] = "/usr/lib/jvm/java-8-openjdk-amd64"
os.environ["SPARK_HOME"] = "/content/spark-2.4.2-bin-hadoop2.7"

如果未安装 findspark,请使用以下命令进行安装:

pip install -q findspark
  1. 通过手动指定模式来创建一个示例 DataFrame,并导入 findspark 模块以连接 Jupyter 和 Spark:

    import findspark
    findspark.init()
    import pyspark
    import os
    
  2. 使用以下命令创建 SparkContextSQLContext

    sc = pyspark.SparkContext()
    from pyspark.sql import SQLContext
    sqlc = SQLContext(sc)
    from pyspark.sql import *
    na_schema = Row("Name","Subject","Marks")
    row1 = na_schema("Ankit", "Science",95)
    row2 = na_schema("Ankit", "Maths", 86)
    row3 = na_schema("Preity", "Maths", 92)
    na_list = [row1, row2, row3]
    df_na = sqlc.createDataFrame(na_list)
    type(df_na)
    

    输出如下:

    pyspark.sql.dataframe.DataFrame
    
  3. 使用以下命令检查 DataFrame:

    df_na.show()
    

    输出如下:

    图 4.29:示例 DataFrame

    图 4.29:示例 DataFrame
  4. 从现有的 RDD 创建一个示例 DataFrame。首先按如下方式创建 RDD:

    data = [("Ankit","Science",95),("Preity","Maths",86),("Ankit","Maths",86)]
    data_rdd = sc.parallelize(data)
    type(data_rdd)
    

    输出如下:

    pyspark.rdd.RDD
    
  5. 使用以下命令将 RDD 转换为 DataFrame:

    data_df = sqlc.createDataFrame(data_rdd)
    data_df.show()
    

    输出如下:

    图 4.30:从 RDD 转换到 DataFrame

    图 4.30:从 RDD 转换到 DataFrame
  6. 通过读取 CSV 文件中的数据来创建一个示例 DataFrame:

    df = sqlc.read.format('com.databricks.spark.csv').options(header='true', inferschema='true').load('mtcars.csv')
    type(df)
    

    输出如下:

    pyspark.sql.dataframe.DataFrame
    
  7. 打印 DataFrame 的前七行:

    df.show(7)
    

    输出如下:

    图 4.31:DataFrame 的前七行
  8. 打印 DataFrame 的模式:

    df.printSchema()
    
  9. 输出如下:

    图 4.32:DataFrame 的架构
  10. 打印 DataFrame 中的列数和行数:

    print('number of rows:'+ str(df.count()))
    print('number of columns:'+ str(len(df.columns)))
    

    输出如下:

    number of rows:32
    number of columns:11
    
  11. 打印 DataFrame 和任意两列的汇总统计:

    df.describe().show()
    

    输出如下:

    图 4.33:DataFrame 的汇总统计

    打印任意两列的汇总:

    df.describe(['mpg','cyl']).show()
    

    输出如下:

    图 4.34:mpg 和 cyl 列的汇总统计
  12. 将样本 DataFrame 的首几行写入 CSV 文件:

    df_p = df.toPandas()
    df_p.head(7).to_csv("mtcars_head.csv")
    

活动 10:使用 Spark DataFrame 进行数据操作

  1. 按照下面所示安装相关包:

    !apt-get install openjdk-8-jdk-headless -qq > /dev/null
    !wget -q http://www-us.apache.org/dist/spark/spark-2.4.0/spark-2.4.0-bin-hadoop2.7.tgz
    !tar xf spark-2.4.0-bin-hadoop2.7.tgz
    !pip install -q findspark
    import os
    os.environ["JAVA_HOME"] = "/usr/lib/jvm/java-8-openjdk-amd64"
    os.environ["SPARK_HOME"] = "/content/spark-2.4.0-bin-hadoop2.7"
    
  2. 然后,导入findspark模块,使用以下命令将 Jupyter 与 Spark 连接:

    import findspark
    findspark.init()
    import pyspark
    import os
    
  3. 现在,创建SparkContextSQLContext,如下面所示:

    sc = pyspark.SparkContext()
    from pyspark.sql import SQLContext
    sqlc = SQLContext(sc)
    
  4. 按照下面所示在 Spark 中创建 DataFrame:

    df = sqlc.read.format('com.databricks.spark.csv').options(header='true', inferschema='true').load('mtcars.csv')
    df.show(4)
    

    输出如下:

    图 4.35:Spark 中的 DataFrame
  5. 使用以下命令重命名 DataFrame 中的任意五列:

    data = df
    new_names = ['mpg_new', 'cyl_new', 'disp_new', 'hp_new', 'drat_new']
    for i,z in zip(data.columns[0:5],new_names):
        data = data.withColumnRenamed(str(i),str(z))
    
    data.columns
    

    输出如下:

    图 4.36:DataFrame 的列
  6. 从 DataFrame 中选择任意两列数值型数据和一列类别型数据:

    data = df.select(['cyl','mpg','hp'])
    data.show(5)
    

    输出如下:

    图 4.37:DataFrame 中的两列数值型和一列类别型数据
  7. 统计类别型变量中不同类别的数量:

    data.select('cyl').distinct().count() #3
    
  8. 通过对两列数值型数据求和和相乘,创建 DataFrame 中的两个新列:

    data = data.withColumn('colsum',(df['mpg'] + df['hp']))
    data = data.withColumn('colproduct',(df['mpg'] * df['hp']))
    data.show(5)
    

    输出如下:

    图 4.38:DataFrame 中的新列
  9. 删除原始数值型的两列:

    data = data.drop('mpg','hp')
    data.show(5)
    

    图 4.39:删除列后的 DataFrame 中的新列
  10. 按类别列对数据进行排序:

    data = data.orderBy(data.cyl)
    data.show(5)
    

    输出如下:

    图 4.40:按类别列排序的数据
  11. 计算categorical变量中每个不同类别的求和列的mean

    data.groupby('cyl').agg({'colsum':'mean'}).show()
    

    输出如下:

    图 4.41:求和列的均值
  12. 过滤出所有大于前一步骤中计算出的mean的行:

    data.count()#15
    cyl_avg = data.groupby('cyl').agg({'colsum':'mean'})
    avg = cyl_avg.agg({'avg(colsum)':'mean'}).toPandas().iloc[0,0]
    data = data.filter(data.colsum > avg)
    data.count()
    data.show(5)
    

    输出如下:

    图 4.42:所有求和列均值的平均值
  13. 对结果 DataFrame 进行去重,以确保它包含所有唯一记录:

    data = data.dropDuplicates()
    data.count()
    

    输出结果是15

活动 11:Spark 中的图形

  1. 在 Jupyter Notebook 中导入所需的 Python 库:

    import pandas as pd
    import os
    import matplotlib.pyplot as plt
    import seaborn as sns
    %matplotlib inline
    
  2. 使用以下命令读取并显示 CSV 文件中的数据:

    df = pd.read_csv('mtcars.csv')
    df.head()
    

    输出如下:

    图 4.43:Auto-mpg DataFrame
  3. 使用直方图可视化数据集中任何连续数值变量的离散频率分布:

    plt.hist(df['mpg'], bins=20)
    plt.ylabel('Frequency')
    plt.xlabel('Values')
    plt.title('Frequency distribution of mpg')
    plt.show()
    

    输出如下:

    图 4.44:离散频率分布直方图

    图 4.44:离散频率分布直方图
  4. 使用饼图可视化数据集中各类别的百分比份额:

    ## Calculate count of records for each gear
    data = pd.DataFrame([[3,4,5],df['gear'].value_counts().tolist()]).T
    data.columns = ['gear','gear_counts']
    ## Visualising percentage contribution of each gear in data using pie chart
    plt.pie(data.gear_counts, labels=data.gear, startangle=90, autopct='%.1f%%')
    plt.title('Percentage contribution of each gear')
    plt.show()
    

    输出如下:

    图 4.45:使用饼图显示类别的百分比份额
  5. 使用箱线图绘制连续变量在分类变量各类别下的分布:

    sns.boxplot(x = 'gear', y = 'disp', data = df)
    plt.show()
    

    输出如下:

    图 4.46: 使用箱线图绘制连续变量的分布

    图 4.46: 使用箱线图绘制连续变量的分布
  6. 使用折线图可视化连续数值变量的值:

    data = df[['hp']]
    data.plot(linestyle='-')
    plt.title('Line Chart for hp')
    plt.ylabel('Values')
    plt.xlabel('Row number')
    plt.show()
    

    输出如下:

    图 4.47: 使用折线图绘制连续数值变量

    图 4.47: 使用折线图绘制连续数值变量
  7. 在同一折线图上绘制多个连续数值变量的值:

    data = df[['hp','disp', 'mpg']]
    data.plot(linestyle='-')
    plt.title('Line Chart for hp, disp & mpg')
    plt.ylabel('Values')
    plt.xlabel('Row number')
    plt.show()
    

    输出如下:

图 4.48: 多个连续数值变量

图 4.48: 多个连续数值变量

第五章: Spark 中的缺失值处理和相关性分析

活动 12: 缺失值处理和使用 PySpark DataFrame 进行相关性分析

  1. 在 Jupyter notebook 中导入所需的库和模块,如下所示:

    import findspark
    findspark.init()
    import pyspark
    import random
    
  2. 在 Jupyter notebook 中使用以下命令设置 SparkContext

    sc = pyspark.SparkContext(appName = "chapter5")
    
  3. 同样,在 notebook 中设置 SQLContext

    from pyspark.sql import SQLContext
    sqlc = SQLContext(sc)
    
  4. 现在,使用以下命令将 CSV 数据读取到 Spark 对象中:

    df = sqlc.read.format('com.databricks.spark.csv').options(header = 'true', inferschema = 'true').load('iris.csv')
    df.show(5)
    

    输出如下:

    图 5.14: Iris 数据框,将 CSV 数据读取到 Spark 对象中

    图 5.14: Iris 数据框,将 CSV 数据读取到 Spark 对象中
  5. 用列的均值填充 Sepallength 列中的缺失值。

  6. 首先,使用以下命令计算 Sepallength 列的均值:

    from pyspark.sql.functions import mean
    avg_sl = df.select(mean('Sepallength')).toPandas()['avg(Sepallength)']
    
  7. 现在,用列的均值填补 Sepallength 列中的缺失值,如下所示:

    y = df
    y = y.na.fill(float(avg_sl),['Sepallength'])
    y.describe().show(1)
    

    输出如下:

    图 5.15: Iris 数据框

    图 5.15: Iris 数据框
  8. 计算数据集的相关矩阵。确保导入所需的模块,如下所示:

    from pyspark.mllib.stat import Statistics
    import pandas as pd
    
  9. 现在,在计算相关性之前,先填充 DataFrame 中的缺失值:

    z = y.fillna(1)
    
  10. 接下来,从 PySpark DataFrame 中删除 String 类型的列,如下所示:

    a = z.drop('Species') 
    features = a.rdd.map(lambda row: row[0:])
    
  11. 现在,在 Spark 中计算相关矩阵:

    correlation_matrix = Statistics.corr(features, method="pearson")
    
  12. 接下来,使用以下命令将相关矩阵转换为 pandas DataFrame:

    correlation_df = pd.DataFrame(correlation_matrix)
    correlation_df.index, correlation_df.columns = a.columns, a.columns
    correlation_df
    

    输出如下:

    图 5.16: 将相关矩阵转换为 pandas DataFrame

    图 5.16: 将相关矩阵转换为 pandas DataFrame
  13. 绘制显示强正相关的变量对,并在其上拟合一条线性回归线。

  14. 首先,将数据从 Spark DataFrame 加载到 pandas DataFrame:

    import pandas as pd
    dat = y.toPandas()
    type(dat)
    

    输出如下:

    pandas.core.frame.DataFrame
    
  15. 接下来,使用以下命令加载所需的模块并绘制数据:

    import matplotlib.pyplot as plt
    import seaborn as sns
    %matplotlib inline
    sns.lmplot(x = "Sepallength", y = "Petallength", data = dat)
    plt.show()
    

    输出如下:

    图 5.17: Seaborn 绘图,x = “Sepallength”,y = “Petallength”

    图 5.17: Seaborn 绘图,x = "Sepallength",y = "Petallength"
  16. 绘制图形,使得 x 等于 Sepallengthy 等于 Petalwidth

    import seaborn as sns
    sns.lmplot(x = "Sepallength", y = "Petalwidth", data = dat)
    plt.show()
    

    输出如下:

    图 5.18: Seaborn 图,x = “Sepallength”,y = “Petalwidth”

    图 5.18: Seaborn 图,x = "Sepallength",y = "Petalwidth"
  17. 绘制图表,使得x等于Petalwidthy等于Petalwidth

    sns.lmplot(x = "Petallength", y = "Petalwidth", data = dat)
    plt.show()
    

    输出如下:

图 5.19: Seaborn 图,x = “Petallength”,y = “Petalwidth”

图 5.19: Seaborn 图,x = "Petallength",y = "Petalwidth"

第六章:业务流程定义与探索性数据分析

活动 13:根据给定数据对数值特征进行高斯分布映射

  1. 下载bank.csv。现在,使用以下命令读取其中的数据:

    import numpy as np
    import pandas as pd
    import seaborn as sns
    import time
    import re
    import os
    import matplotlib.pyplot as plt
    sns.set(style="ticks")
    # import libraries required for preprocessing
    import sklearn as sk
    from scipy import stats
    from sklearn import preprocessing
    # set the working directory to the following
    os.chdir("/Users/svk/Desktop/packt_exercises")
    # read the downloaded input data (marketing data)
    df = pd.read_csv('bank.csv', sep=';')
    
  2. 从数据框中识别数值数据。数据可以根据其类型进行分类,例如类别型、数值型(浮动、整数)、日期等。我们在这里识别数值数据,因为我们只能对数值数据进行归一化:

    numeric_df = df._get_numeric_data()
    numeric_df.head()
    

    输出如下:

    图 6.12: 数据框

    图 6.12: 数据框
  3. 进行正态性测试,并识别具有非正态分布的特征:

    numeric_df_array = np.array(numeric_df) # converting to numpy arrays for more efficient computation
    loop_c = -1
    col_for_normalization = list()
    for column in numeric_df_array.T:
        loop_c+=1
        x = column
        k2, p = stats.normaltest(x) 
        alpha = 0.001
        print("p = {:g}".format(p))
    
        # rules for printing the normality output
        if p < alpha:
            test_result = "non_normal_distr"
            col_for_normalization.append((loop_c)) # applicable if yeo-johnson is used
    
            #if min(x) > 0: # applicable if box-cox is used
                #col_for_normalization.append((loop_c)) # applicable if box-cox is used
            print("The null hypothesis can be rejected: non-normal distribution")
    
        else:
            test_result = "normal_distr"
            print("The null hypothesis cannot be rejected: normal distribution")
    

    输出如下:

    图 6.13: 正态性测试并识别特征

    注意

    此处进行的正态性测试基于 D'Agostino 和 Pearson 的测试(docs.scipy.org/doc/scipy/reference/generated/scipy.stats.normaltest.html),该测试结合了偏度和峰度来识别特征的分布与高斯分布的接近程度。在此测试中,如果 p 值小于设定的 alpha 值,则拒绝零假设,表明该特征不具有正态分布。我们通过循环函数查看每一列,并识别每个特征的分布。

  4. 绘制特征的概率密度图,以便直观地分析其分布:

    columns_to_normalize = numeric_df[numeric_df.columns[col_for_normalization]]
    names_col = list(columns_to_normalize)
    # density plots of the features to check the normality
    columns_to_normalize.plot.kde(bw_method=3)
    

    检查正态性的特征密度图如下所示:

    图 6.14: 特征图

    图 6.14: 特征图

    注意

    多个变量的密度图在前面的图表中展示。图表中各特征的分布具有较高的正峰度,这不是正态分布。

  5. 准备幂变换模型,并对已识别的特征进行变换,以便根据box-coxyeo-johnson方法将其转换为正态分布:

    pt = preprocessing.PowerTransformer(method='yeo-johnson', standardize=True, copy=True)
    normalized_columns = pt.fit_transform(columns_to_normalize)
    normalized_columns = pd.DataFrame(normalized_columns, columns=names_col)
    

    在之前的命令中,我们准备了幂变换模型,并将其应用于所选特征的数据。

  6. 在变换之后,再次绘制特征的概率密度图,以直观地分析特征的分布:

    normalized_columns.plot.kde(bw_method=3)
    

    输出如下:

图 6.15: 特征图

图 6.15: 特征图

第七章:大数据分析中的可重复性

活动 14:测试数据属性(列)的正态性,并对非正态分布的属性进行高斯归一化

  1. 在 Jupyter 笔记本中导入所需的库和包:

    import numpy as np
    import pandas as pd
    import seaborn as sns
    import time
    import re
    import os
    import matplotlib.pyplot as plt
    sns.set(style="ticks")
    
  2. 现在,导入预处理所需的库:

    import sklearn as sk
    from scipy import stats
    from sklearn import preprocessing
    
  3. 使用以下命令设置工作目录:

    os.chdir("/Users/svk/Desktop/packt_exercises")
    
  4. 现在,将数据集导入到 Spark 对象中:

    df = pd.read_csv('bank.csv', sep=';')
    
  5. 识别数据中的目标变量:

    DV = 'y'
    df[DV]= df[DV].astype('category')
    df[DV] = df[DV].cat.codes
    
  6. 使用以下命令生成训练和测试数据:

    msk = np.random.rand(len(df)) < 0.8
    train = df[msk]
    test = df[~msk]
    
  7. 创建YX数据,如下所示:

    # selecting the target variable (dependent variable) as y
    y_train = train[DV]
    
  8. 使用drop命令删除DVy

    train = train.drop(columns=[DV])
    train.head()
    

    输出如下:

    图 7.22:银行数据集

    图 7.22:银行数据集
  9. 对数据进行数值和分类分割,并对数值数据执行分布变换:

    numeric_df = train._get_numeric_data()
    

    对数据进行预处理:

  10. 现在,创建一个loop,使用以下命令识别具有非正态分布的列(将数据转换为 NumPy 数组,以提高计算效率):

    numeric_df_array = np.array(numeric_df)
    loop_c = -1
    col_for_normalization = list()
    for column in numeric_df_array.T:
        loop_c+=1
        x = column
        k2, p = stats.normaltest(x) 
        alpha = 0.001
        print("p = {:g}".format(p))
    
        # rules for printing the normality output
        if p < alpha:
            test_result = "non_normal_distr"
            col_for_normalization.append((loop_c)) # applicable if yeo-johnson is used
    
            #if min(x) > 0: # applicable if box-cox is used
                #col_for_normalization.append((loop_c)) # applicable if box-cox is used
            print("The null hypothesis can be rejected: non-normal distribution")
    
        else:
            test_result = "normal_distr"
            print("The null hypothesis cannot be rejected: normal distribution")
    

    输出如下:

    图 7.23:识别具有非线性分布的列

    图 7.23:识别具有非线性分布的列
  11. 创建基于PowerTransformer的变换(box-cox):

    pt = preprocessing.PowerTransformer(method='yeo-johnson', standardize=True, copy=True)
    

    注意

    box-cox仅能处理正值。

  12. 对数据应用幂变换模型。选择需要标准化的列:

    columns_to_normalize = numeric_df[numeric_df.columns[col_for_normalization]]
    names_col = list(columns_to_normalize)
    
  13. 创建密度图以检查正态性:

    columns_to_normalize.plot.kde(bw_method=3)
    

    输出如下:

    图 7.24:密度图检查正态性

    图 7.24:密度图检查正态性
  14. 现在,使用以下命令将列转换为正态分布:

    normalized_columns = pt.fit_transform(columns_to_normalize)
    normalized_columns = pd.DataFrame(normalized_columns, columns=names_col)
    
  15. 再次创建密度图,以检查正态性:

    normalized_columns.plot.kde(bw_method=3)
    

    输出如下:

    图 7.25:另一个密度图检查正态性

    图 7.25:另一个密度图以检查正态性
  16. 使用loop在转换后的数据上识别具有非正态分布的列:

    numeric_df_array = np.array(normalized_columns) 
    loop_c = -1
    for column in numeric_df_array.T:
        loop_c+=1
        x = column
        k2, p = stats.normaltest(x) 
        alpha = 0.001
        print("p = {:g}".format(p))
    
        # rules for printing the normality output
        if p < alpha:
            test_result = "non_normal_distr"
            print("The null hypothesis can be rejected: non-normal distribution")
    
        else:
            test_result = "normal_distr"
            print("The null hypothesis cannot be rejected: normal distribution")
    

    输出如下:

    图 7.26:幂变换模型应用于数据

    图 7.26:幂变换模型应用于数据
  17. 绑定标准化和非标准化列。选择不进行标准化的列:

    columns_to_notnormalize = numeric_df
    columns_to_notnormalize.drop(columns_to_notnormalize.columns[col_for_normalization], axis=1, inplace=True)
    
  18. 使用以下命令绑定非标准化列和标准化列:

    numeric_df_normalized = pd.concat([columns_to_notnormalize.reset_index(drop=True), normalized_columns], axis=1)
    numeric_df_normalized
    

图 7.27:非标准化和标准化列

图 7.27:非标准化和标准化列

第八章:创建完整的分析报告

活动 15:使用 Plotly 生成可视化

  1. 将所有必需的库和包导入到 Jupyter 笔记本中。确保将数据从bank.csv读取到 Spark DataFrame 中。

  2. 导入 Plotly 库,如下所示:

    import plotly.graph_objs as go
    from plotly.plotly import iplot
    import plotly as py
    
  3. 现在,为了在 Plotly 中进行可视化,我们需要启动一个离线会话。使用以下命令(要求版本>= 1.9.0):

    from plotly import __version__
    from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
    print(__version__)
    
  4. 现在,Plotly 已启动离线模式。使用以下命令启动 Plotly 笔记本:

    import plotly.plotly as py
    import plotly.graph_objs as go
    init_notebook_mode(connected=True)
    

    启动 Plotly 笔记本后,我们可以使用 Plotly 生成多种类型的图表,如条形图、箱型图或散点图,并将整个输出转换为一个用户界面或一个支持 Python Flask 框架的应用程序。

  5. 现在,使用 Plotly 绘制每个图表:

    柱状图:

    df = pd.read_csv('bank.csv', sep=';')
    data = [go.Bar(x=df.y,
                y=df.balance)]
    py.iplot(data)
    

    柱状图如下所示:

图 8.18:柱状图

散点图:

py.iplot([go.Histogram2dContour(x=df.balance, y=df.age, contours=dict(coloring='heatmap')),
       go.Scatter(x=df.balance, y=df.age, mode='markers', marker=dict(color='red', size=8, opacity=0.3))], show_link=False)

散点图如下所示:

图 8.19:散点图

图 8.19:散点图

箱形图:

plot1 = go.Box(
    y=df.age,
    name = 'age of the customers',
    marker = dict(
        color = 'rgb(12, 12, 140)',
    )
)
py.iplot([plot1])

箱形图如下所示:

图 8.20:箱形图

图 8.20:箱形图
posted @ 2025-07-20 11:31  绝不原创的飞龙  阅读(4)  评论(0)    收藏  举报