Python-数据可视化指南-全-

Python 数据可视化指南(全)

原文:Data Visualization With Python

协议:CC BY-NC-SA 4.0

零、前言

关于

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

关于书

您将从 Python 开始数据可视化,首先介绍数据可视化及其重要性。然后,您将通过计算一些数字的平均值、中值和方差,并观察它们值的差异来学习统计学。您还将了解关键的 NumPy 和 Pandas 技术,例如索引、切片、迭代、过滤和分组。接下来,您将研究不同类型的可视化,比较它们,并找出如何使用这种比较来选择特定类型的可视化。你将探索不同的绘图,包括自定义创作。

掌握了各种可视化库之后,您将学会使用 Matplotlib 和 Seaborn 来简化创建可视化的过程。还将向您介绍先进的可视化技术,如地质图和交互式绘图。您将学习如何理解地理空间数据,创建可集成到任何网页中的交互式可视化,并利用任何数据集来构建美观且有洞察力的可视化。您将学习如何使用 Choropleth 绘图在地图上绘制地理空间数据,并学习 Bokeh 的基础知识,通过添加小部件和制作信息显示动画来扩展绘图。

这本书以一个有趣的活动结束,在这个活动中,你将获得一个新的数据集,你必须应用你所学的一切来创建一个有洞察力的顶点可视化。

关于作者

马里奥·德布勒是斯图加特大学专注于深度学习的博士生。他此前曾在硅谷博世人工智能中心实习,从事深度学习领域,使用最先进的算法开发尖端产品。在他的硕士论文中,他致力于将深度学习应用于医学数据,以推动医学应用。

Tim gromann是一名 CS 学生,对从 AI 到 IoT 的各种话题都很感兴趣。他之前在硅谷博世人工智能中心工作,从事大数据工程领域。他高度参与不同的开源项目,并在会议上积极谈论他的项目和经验。

目标

  • 获得各种图及其最佳用例的概述
  • 使用不同的绘图库,了解它们的优缺点
  • 了解如何创建有洞察力的可视化效果
  • 理解什么是好的可视化
  • 提高您的 Python 数据争论技巧
  • 使用真实世界的数据
  • 学习行业标准工具
  • 发展您对数据格式和表示的一般理解

观众

这本书是针对开发人员或科学家谁想进入数据科学或想使用数据可视化来丰富他们的个人和专业项目。不需要数据分析和可视化方面的经验;但是,推荐一些 Python 和高中水平数学的知识。尽管这是一本关于数据可视化的初级书籍,但更有经验的学生将受益于通过处理真实世界的数据来提高他们的 Python 技能。

进场

这本书用通俗易懂的语言彻底解释了这项技术,同时完美地平衡了理论和练习。每章都是在前一章的基础上设计的。这本书还包含多种活动,这些活动使用现实生活中的业务场景,让你在高度相关的环境中练习和应用你的新技能。

最低硬件要求

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

  • 操作系统:Windows 7 SP1 32/64 位、Windows 8.1 32/64 位或 Windows 10 32/64 位、Ubuntu 14.04 或更高版本、或 macOS Sierra 或更高版本
  • 处理器:双核或更好
  • 内存:4GB 内存
  • 存储:10 GB 可用空间

软件需求

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

  • 浏览器:谷歌 Chrome 还是 Mozilla 火狐
  • 伯爵夫人
  • JupyterLab 和 Jupyter 笔记本
  • 升华文本(最新版本)、原子集成开发环境(最新版本)或其他类似的文本编辑器应用
  • 蟒蛇 3
  • 安装了以下 Python 库:NumPy、pandas、Matplotlib、seaborn、geoplotlib、Bokeh 和 squarify

惯例

文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、伪 URL、用户输入和 Twitter 句柄如下所示:“axis=0 是水平的,axis=1是垂直的,所以如果我们想要每行都有结果,就需要选择axis=1

代码块设置如下:

# indexing the first value of the second row (1st row, 1st value)
first_val_first_row = dataset[0][0]
np.mean(first_val_first_row)

新术语和重要词汇以粗体显示:

“为了从可视化数据中得出结论,我们需要处理我们的数据,并将其转换为最佳的表示形式。这就是数据角力的用武之地。”

安装和设置

在您开始阅读本书之前,我们将安装 Python 3。 6、pip 以及本书通篇使用的其他库。您可以在这里找到安装它们的步骤。

安装 Python

按照此链接中的说明安装 Python 3.6:https://realpython.com/installing-python/

安装管

  1. 要安装 pip,请转到以下链接并下载get-pip.py文件:https://pip.pypa.io/en/stable/installing/

  2. Then, use the following command to install it:

    python get-pip.py
    

    您可能需要使用python3 get-pip.py命令,因为您的计算机上以前版本的 Python 已经使用了python命令。

安装库

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

python -m pip install --user numpy matplotlib jupyterlab pandas squarify bokeh geoplotlib seaborn

使用 JupyterLab 和 Jupyter 笔记本

你将在 JupyterLab 进行不同的练习和活动。这些练习和活动可以从相关的 GitHub 存储库中下载。

从这里下载资源库:https://github . com/trainingypbackt/Data-Visualization-with-Python

您可以使用 GitHub 下载,也可以通过点击右上角的绿色克隆或下载按钮作为压缩文件夹下载。

为了打开 Jupyter 笔记本,你必须用你的终端遍历目录。为此,请键入:

cd Data-Visualization-with-Python/<your current chapter>

例如:

cd Data-Visualization-with-Python/chapter01/

要完成该过程,请执行以下步骤:

  1. 要到达每个活动和练习,您必须再次使用cd进入每个文件夹,如:

    cd Activity01
    
  2. 进入自己选择的文件夹后,只需调用jupyter-lab即可启动 JupyterLab。同样,对于 Jupyter 笔记本,调用jupyter notebook

导入 Python 库

本书中的每一项练习和活动都将利用各种图书馆。将库导入 Python 非常简单,我们是这样做的:

  1. 要导入库,比如 NumPy 和 pandas,我们必须运行以下代码。这会将整个numpy库导入到我们当前的文件中:

    import numpy              # import numpy
    
  2. 在本书练习和活动的第一个单元中,您将看到以下代码。我们可以在代码中使用np代替numpynumpy :

    import numpy as np        # import numpy and assign alias np
    

    调用方法

  3. 在后面的章节中,将会出现部分导入,如下面的代码所示。这只是从库中加载mean方法:

    from numpy import mean    # only import the mean method of numpy
    

安装代码包

将该类的代码包复制到C:/Code文件夹。

附加资源

本书的代码包也托管在 GitHub 上,网址为:https://GitHub . com/trainingypbackt/Data-Visualization-with-Python

我们还有来自 https://github.com/PacktPublishing/丰富的书籍和视频目录的其他代码包。看看他们!

一、数据可视化和数据探索的重要性

学习目标

本章结束时,您将能够:

  • 解释为什么数据可视化很重要
  • 计算基本统计值,如中值、平均值和方差
  • 使用 NumPy 进行数据争论
  • 用熊猫来争论数据

在这一章中,你还将了解 NumPy 和熊猫的基本操作。

简介

与机器不同,人们通常不具备从给定数据中的一组随机数字和信息中解读大量信息的能力。虽然他们可能知道数据的基本组成,但他们可能需要帮助才能完全理解它。在我们所有的逻辑能力中,我们通过视觉信息的处理最能理解事物。当数据被可视化表示时,理解复杂构建和数字的概率会增加。

Python 最近作为一种编程语言出现,在数据分析方面表现良好。Python 有跨数据科学管道的应用,可以将数据转换成可用的格式,对其进行分析,并从数据中提取有用的结论来很好地表示它。它提供了数据可视化库,可以帮助您快速组装图形表示。

在本书中,您将学习如何将 Python 与各种库结合使用,例如 NumPy熊猫Matplotlibseaborngeo lotlib,以使用真实世界的数据创建有影响力的数据可视化。除此之外,你还将了解不同类型图表的特点,并比较它们的优缺点。这将帮助您选择适合可视化数据的图表类型。

一旦我们理解了基础知识,我们就可以涵盖更高级的概念,例如交互式可视化以及如何使用 Bokeh 来创建讲述故事的动画可视化。完成本书后,您将能够执行数据辩论,提取重要信息,并以描述的方式可视化您的见解。

数据可视化简介

电脑和智能手机以数字格式存储姓名和数字等数据。数据表示是指可以存储、处理和传输数据的形式。

陈述可以讲述一个故事,并向你的观众传达重要的发现。如果不适当地对你的信息进行建模,用它来做出有意义的发现,它的价值就会降低。创建表示有助于实现更清晰、更简洁、更直接的信息视角,使任何人都更容易理解数据。

信息并不真正等同于数据。表示是发现隐藏在数据中的见解的有用工具。因此,表示将信息转换成有用的数据。

数据可视化的重要性

与任何其他形式的数据相比,可视化数据非常容易理解。通过使用可视化,我们可以更好地了解数据包含的内容,而不仅仅是查看 Excel 电子表格列中的数据。例如,很容易从下图给出的数字数据中看到一种模式:

Figure 1.1: A simple example of data visualization

图 1.1:数据可视化的一个简单例子

可视化数据有许多优点,例如:

  • 复杂的数据很容易理解
  • 可以创建异常值、目标受众和未来市场的简单可视化表示
  • 讲故事可以用仪表盘和动画来完成
  • 可以通过交互式可视化来探索数据

数据角力

为了从可视化数据中得出结论,我们需要处理我们的数据,并将其转换为最佳的表示形式。这就是数据角力的用武之地。它是以一种允许机器学习算法显示和理解数据的方式来扩充、转换和丰富数据的学科。

查看下面的数据争论流程图,了解如何获得准确且可操作的数据,供业务分析师处理。如您所见,员工敬业度数据最初是原始形式。它将作为数据帧导入,稍后将被清理。然后,清理后的数据被转换成相应的图形,从中可以对见解进行建模。基于这些见解,我们可以交流最终结果。例如,可以根据从反馈调查、员工任期、离职面谈、一对一会议等收集的原始数据来衡量员工敬业度。这些数据经过清理,并根据推荐、对领导的信任和晋升范围等参数制成图表。百分比,即从图表中产生的见解,有助于我们得出结果,从而确定员工敬业度的衡量标准:

Figure 1.2: Data wrangling process to measure employee engagement

图 1.2:衡量员工敬业度的数据争论过程

可视化工具和库

有几种方法可以创建数据可视化。根据您的背景,您可能希望使用非编码工具,如 Tableau ,它使您能够对数据有一个良好的感觉。除了将在本书中使用的 Python 之外, MATLABR 在数据分析中被大量使用。

然而,Python 是业内最流行的语言。它的易用性和您可以操作和可视化数据的速度,加上大量库的可用性,使 Python 成为最佳选择。

注意

MATLAB(https://www.mathworks.com/products/matlab.html)、R(https://www.r-project.org)和 Tableau(https://www.tableau.com)都不是本书的一部分,所以我们只涵盖重点介绍的 Python 工具和库。

统计概述

统计学是数值数据的分析、收集、解释和表示的组合。概率是对事件发生可能性的度量,量化为 0 到 1 之间的数字。

一个概率分布是一个为每个可能事件提供概率的函数。概率分布经常用于统计分析。概率越高,事件发生的可能性越大。概率分布有两种类型,即离散概率分布和连续概率分布。

一个离散概率分布显示了一个随机变量可以取的所有值,以及它们的概率。下图说明了离散概率分布的一个示例。如果我们有一个 6 面骰子,我们可以在 1 和 6 之间滚动每个数字。根据滚动的数量,我们有六个事件可以发生。滚动任何数字的概率相等,六个事件中任何一个发生的概率为 1/6:

Figure 1.3: Discrete probability distribution for die rolls

图 1.3:模具辊的离散概率分布

A 连续概率分布定义了连续随机变量每个可能值的概率。下图说明了连续概率分布的示例。这个例子说明了开车回家所需时间的分布。在大多数情况下,大约需要 60 分钟,但有时需要更少的时间,因为没有交通,有时需要更多的时间,如果有交通堵塞:

Figure 1.4: Continuous probability distribution for the time taken to reach home

图 1.4:到家时间的连续概率分布

中央趋势的度量

中心趋势的度量通常被称为平均值,描述概率分布的中心值或典型值。我们将在本章中讨论三种平均值:

  • Mean: The arithmetic average that is computed by summing up all measurements and dividing the sum by the number of observations. The mean is calculated as follows:

  • 中值:这是有序数据集的中间值。如果有偶数个观察值,中间值将是两个中间值的平均值。与平均值相比,中位数不太容易出现异常值,因为异常值是数据中的独特值。

  • 模式:我们对中枢倾向的最后一个衡量,模式定义为最频繁值。在多个值同样频繁的情况下,可能有多个模式。

示例:

一个骰子滚动了十次,我们得到了以下数字:4、5、4、3、4、2、1、1、2 和 1。

平均值的计算方法是将所有事件相加,然后除以观察次数:(4+5+4+3+4+2+1+1+2+1)/10=2.7。

为了计算中间值,必须根据模辊的值对其进行排序。有序值如下:1,1,1,2,2,3,4,4,4,5。因为我们有偶数个模辊,我们需要取两个中间值的平均值。两个中间值的平均值为(2+3)/2=2.5。

模式是 1 和 4,因为它们是最常见的两个事件。

离散度的度量

离散度,也称为可变性,是概率分布被拉伸或压缩的程度。

分散的不同度量如下:

  • 方差:方差是偏离均值的平方的期望值。它描述了一组数字离它们的平均值有多远。方差计算如下:

  • 标准差:是方差的平方根。
  • 范围:这是一个数据集中最大值和最小值的差值。
  • 四分位数区间:也叫中间延伸中间 50% ,是第 75 和第 25 个百分位数之间的差值,或上下四分位数之间的差值。

相关性

到目前为止,我们讨论的度量只考虑了单个变量。相比之下,相关性描述了两个变量之间的统计关系:

在正相关中,两个变量都向同一个方向移动

在负相关中,变量向相反的方向移动

在零相关中,变量是不相关的

注意

你应该知道的一件事是,相关性并不意味着因果关系。相关性描述两个或多个变量之间的关系,而因果关系描述一个事件是如何由另一个事件引起的。例如:穿着鞋子睡觉与醒来时头痛有关。这并不意味着穿着鞋子睡觉会导致早上头痛。可能还有第三个隐藏的变量,例如,有人前一天晚上工作到很晚,这导致他们都穿着鞋子睡着了,醒来时头疼。

示例:

你想找一个体面的公寓出租,与你已经找到的其他公寓相比,不要太贵。你在网站上找到的其他公寓价格如下:每月 700 美元、850 美元、1500 美元和 750 美元:

  • 平均值为
  • 中位数为
  • 标准偏差为
  • 范围为
  • 在这种情况下,中位数是一个更好的统计指标,因为它不太容易出现异常值(租金为 1,500 美元)。

数据类型

了解您正在处理的数据类型非常重要,这样您就可以选择正确的统计度量和正确的可视化。我们将数据分为分类/定性和数字/定量。分类数据描述特征,例如,物体的颜色或一个人的性别。我们可以进一步将分类数据分为名义数据和序数数据。与名义数据相反,序数数据是有顺序的。

数值数据可以分为离散数据和连续数据。如果数据只能有某些值,我们就称之为离散数据,而连续数据可以取任何值(有时限于一个范围)。

另一个要考虑的方面是数据是否有一个时间域——换句话说,它是受时间的约束还是随着时间的推移而变化?如果数据绑定到某个位置,显示空间关系可能会很有趣,因此您也应该记住这一点:

Figure 1.5: Classification of types of data

图 1.5:数据类型的分类

汇总统计

在现实应用中,我们经常会遇到巨大的数据集,因此汇总统计用于汇总数据的重要方面。它们是以简洁明了的方式交流大量信息所必需的。

我们已经讨论了中心趋势和离差的度量,它们都是汇总统计。重要的是要知道,中心趋势的度量显示了一组数据值中的中心点,而离差的度量显示了数据值的分布情况。

下表概述了哪种中心趋势度量最适合特定类型的数据:

Figure 1.6: Best suited measures of central tendency for different data types

图 1.6:不同类型数据的中心趋势的最适合度量

NumPy

在处理数据时,我们经常需要一种处理多维数组的方法。正如我们之前讨论的,我们还必须对这些数据应用一些基本的数学和统计运算。这正是 NumPy 定位自己的地方。它支持大型 n 维数组,是许多高级数学和统计运算的内置支持。

注意

在 NumPy 之前,有一个名为 Numeric 的库。然而,它不再被使用,因为 NumPy 的签名 ndarray 允许对大型和高维矩阵进行高效处理。

那些数组是 NumPy 的本质。它们比使用 Python 的内置列表更快。除了内置的列表数据类型之外,ndarrays 还提供了一个跨越式的内存视图(例如,Java 中的int[])。因为它们是均匀类型的,这意味着所有的元素必须是相同的类型,所以步幅是一致的,这导致更少的内存浪费和更好的访问时间。

一个步距是一个数组中两个相邻元素开始之间的位置数。它们通常以字节或数组元素的大小单位来度量。步幅可以大于或等于元素的大小,但不能小于元素的大小,否则它将与下一个元素的内存位置相交。

注意

请记住,NumPy 数组具有“已定义”的数据类型。这意味着您不能将字符串插入整数类型数组。NumPy 主要用于双精度数据类型。

练习 1:加载样本数据集并计算平均值

注意

所有练习和活动都将在 Jupyter 笔记本中开发。请从https://GitHub . com/trainingypbackt/Data-Visualization-with-Python下载带有所有准备好的模板的 GitHub 资源库

在本练习中,我们将加载normal_distribution.csv数据集,并计算其中每行和每列的平均值:

  1. Open the exercise01.ipynb Jupyter Notebook from the Lesson01 folder to implement this exercise.

    为此,您需要导航到该文件的路径。在命令行终端中,键入jupyter-lab.

  2. 现在,您将看到一个浏览器窗口打开,显示您在其中调用上一个命令的目录内容。点击exercise01.ipynb。这将打开笔记本。

  3. 第 01 章的笔记本现在应该已经打开,可以修改了。简短介绍后,您应该会看到一个导入必要依赖项的单元格。在这种情况下,我们将导入别名为

    # importing the necessary dependencies
    import numpy as np
    

    numpy

  4. Look for the cell that has a comment saying, "loading the dataset." This is the place you want to insert the genfromtxt method call. This method helps in loading the data from a given text or .csv file.

    完整的行应该如下所示:

    # loading the dataset
    dataset = np.genfromtxt('./data/normal_distribution.csv', delimiter=',')
    
  5. If everything works as expected, the generation should run through without any error or output. Have a look at the data you just imported by simply writing the name of the ndarray in the next cell. This is implemented in the following code. Simply executing a cell that returns a value such as an ndarray GI will use Jupyter formatting, which looks nicer and, in most cases, displays more information than using print:

    # looking at the dataset
    dataset
    

    前面代码的输出如下:

    Figure 1.7: The first rows of the normal_distribution.csv file

    图 1.7:正态分布. csv 文件的前几行
  6. To get a quick overview of our dataset, we want to print out the "shape" of it.

    这将为我们提供表单的输出(行、列)。使用dataset.shape命令打印出形状。我们也可以将行称为实例,将列称为特征。这意味着我们的数据集有 24 个实例和 8 个特征:

    # printing the shape of our dataset
    dataset.shape
    

    前面代码的输出如下:

    Figure 1.8: Shape of the dataset

    图 1.8:数据集的形状
  7. Calculating the mean is the next step once we've loaded and checked our dataset.

    numpy array中的第一行可以通过简单地用零索引来访问,比如:dataset[0]。正如我们之前提到的,NumPy 有一些用于计算的内置函数,例如 mean。因此,我们可以简单地调用np.mean()并传入数据集行来获得结果。打印输出应该如下所示:

    # calculating the mean for the first row
    np.mean(dataset[0])
    

    前面代码的输出如下:

    Figure 1.9: Mean of elements in the first row

    图 1.9:第一行元素的平均值
  8. We can also do the same for the first column by using np.mean() in combination with the column indexing dataset[:, 0]:

    # calculating the mean for the first column
    np.mean(dataset[:, 0])
    

    前面代码的输出如下:

    Figure 1.10: Mean of elements in the first column

    图 1.10:第一列中元素的平均值
  9. If we want to get the mean for every single row, aggregated in a list, we can make use of the axis tools of NumPy. By simply passing the axis parameter in the np.mean() call, we can define the dimension our data will be aggregated on.

    axis=0是水平的,axis=1是垂直的,所以如果我们想要每行都有结果,我们需要选择axis=1:

    # mean for each row
    np.mean(dataset, axis=1)
    

    前面代码的输出如下:

    Figure 1.11: Mean of elements for each row

    图 1.11:每行元素的平均值

    如果要有每一列的结果,需要选择axis=0

    # mean for each column
    np.mean(dataset, axis=0)
    

    前面代码的输出如下:

    Figure 1.12: Mean of elements for each column

    图 1.12:每列元素的平均值
  10. As the last task of this exercise, we also want to have the mean of the whole matrix. We could sum up all the values we retrieved in the previous steps, but NumPy allows us to simply pass in the whole dataset to do this calculation:

```py
# calculating the mean for the whole matrix
np.mean(dataset)
```

前面代码的输出如下:

![Figure 1.13: Mean of elements for the complete dataset ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_13.jpg)
图 1.13:完整数据集的元素平均值

恭喜你!您已经离将 NumPy 与打印库结合使用并创建有影响力的可视化效果又近了一步。由于我们已经介绍了最基本的内容,并在前面的练习中计算了平均值,现在就看您如何解决即将到来的活动了。

活动 1:使用 NumPy 计算给定数字的平均值、中值、方差和标准差

在本练习中,我们将使用所学的技能导入数据集并执行一些基本计算(平均值、中值、方差和标准差)来计算我们的任务。

我们希望巩固我们的新技能并熟悉 NumPy:

  1. Lesson01文件夹打开activity01.ipynb Jupyter 笔记本,执行本活动。

  2. 现在,import numpy进入你的 Jupyter 笔记本,给它取别名np

  3. 使用numpygenfromtxt方法加载normal_distribution.csv数据集。

  4. 看看数据库,确保一切正常。

  5. 给定数据集,进入并使用内置的numpy方法。

  6. 使用这些方法,首先计算第三行、最后一列以及前 3 行和前 3 列的交集的平均值。

  7. 然后,计算最后一行、最后 3 列和每行的中间值。

  8. 计算每列的方差、最后 2 行和前 2 列的交集。

  9. Calculate the standard deviation for the dataset.

    注意:

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

恭喜你!您已经使用 NumPy 完成了第一项活动。在接下来的活动中,这些知识将得到进一步巩固。

基本数值运算

在本节中,我们将学习基本的 NumPy 操作,如索引、切片、拆分和迭代,并在活动中实现它们。

索引

在高级别上,索引 NumPy array中的元素的工作方式与内置 Python 列表相同。因此,我们能够索引多维矩阵中的元素:

dataset[0]       # index single element in outermost dimension
dataset[-1]      # index in reversed order in outermost dimension
dataset[1, 1]    # index single element in two-dimensional data
dataset[-1, -1]  # index in reversed order in two-dimensional data

切片

切片也改编自 Python 的列表。在处理大量数据时,能够轻松地将部分列表分割成新的数组非常有帮助:

dataset[1:3]         # rows 1 and 2
dataset[:2, :2]      # 2x2 subset of the data 
dataset[-1, ::-1]    # last row with elements reversed
dataset[-5:-1, :6:2] # last 4 rows, every other element up to index 6

分裂

拆分数据在许多情况下都是有帮助的,从只绘制一半的时间序列数据到为机器学习算法分离测试和训练数据。

有两种方法可以横向和纵向拆分数据。水平分裂可以用hsplit方法进行。垂直分裂可以用vsplit方法完成:

np.hsplit(dataset, (3))  # split horizontally in 3 equal lists
np.vsplit(dataset, (2))  # split vertically in 2 equal lists

迭代

迭代 NumPy 数据结构 ndarrays 也是可能的。它一个接一个地遍历整个数据列表,访问数组中的每个元素一次。考虑到它们可以有几个维度,索引变得非常复杂。

nditer是在给定数量的数组上迭代的多维迭代器对象:

# iterating over whole dataset (each value in each row)
for x in np.nditer(dataset):
    print(x)

ndenumerate会给我们确切的这个指数,从而为第一行的第二个值返回(01):

# iterating over whole dataset with indices matching the position in the dataset
for index, value in np.ndenumerate(dataset):
    print(index, value) 

活动 2:索引、切片、拆分和迭代

在本练习中,我们将使用 NumPy 的特性来索引、切片、拆分和迭代数组,以巩固我们所学的内容。我们的客户希望我们证明我们的数据集很好地分布在平均值 100 附近:

  1. Lesson01文件夹打开activity02.ipynb Jupyter 笔记本,执行本活动。

  2. 现在,import numpy进入你的 Jupyter 笔记本,给它取别名np

  3. 使用 NumPy 加载normal_distribution.csv数据集。通过查看数据库,确保一切正常,就像在前面的活动中一样。遵循笔记本中的任务描述。

  4. 加载数据集后,使用前面讨论的索引功能对数据集的第二行(第二行)进行索引,对数据集的最后一个元素(最后一行)进行索引,对第二行的第一个值(第二行,第一个值)进行索引,并对倒数第二行的最后一个值进行索引(使用组合访问)。

  5. 创建输入的子列表需要切片。对前两行和前两列的四个元素(2x2)的交集进行切片,选择第五行的每隔一个元素,并颠倒输入顺序,以相反的顺序选择前两行。

  6. 如果我们需要一个更小的数据集,我们也可以使用拆分来有效地划分我们的数据集。使用这个概念将数据集水平分割成三个相等的部分,并在索引 2 上垂直分割数据集。

  7. The last task in this activity will be iterating over the complete dataset. Use the previously discussed methods to iterate over the whole dataset, with indices matching the position in the dataset.

    注意:

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

恭喜你!我们已经介绍了 NumPy 的大多数基本数据争论方法。在下一个活动中,我们将了解更高级的功能,这些功能将为您提供更好地了解数据的工具。

高级数字操作

在本节中,我们将学习 GI 高级 NumPy 操作,如过滤、排序、组合和整形,并在活动中实现它们。

过滤

过滤是一个非常强大的工具,如果你想避免异常值,它可以用来清理你的数据。这也有助于更好地了解您的数据。

除了dataset[dataset > 10]简写符号外,我们还可以使用内置的 NumPy extract方法,它使用不同的符号做同样的事情,但是用更复杂的例子给我们更大的控制力。

如果我们只想提取符合我们给定条件的值的索引,我们可以使用内置的where方法。例如,np.where(dataset > 5)将返回初始数据集中大于 5:

dataset[dataset > 10]                   # values bigger than 10
np.extract((dataset < 3), dataset)      # alternative – values smaller than 3
dataset[(dataset > 5) & (dataset < 10)] # values bigger 5 and smaller 10
np.where(dataset > 5)                   # indices of values bigger than 5 (rows and cols)

排序

对数据集的每一行进行排序非常有用。使用 NumPy,我们还能够对其他维度进行排序,例如列。

此外,argsort为我们提供了获得索引列表的可能性,这将产生排序列表:

np.sort(dataset)          # values sorted on last axis
np.sort(dataset, axis=0)  # values sorted on axis 0
np.argsort(dataset)       # indices of values in sorted list

组合

将同一维度的两个数据集保存到不同的文件中时,将行和列堆叠到现有数据集上会很有帮助。

给定两个数据集,我们使用 vstackdataset_2之上“堆叠”dataset_1,这将给我们一个组合数据集,所有行来自dataset_1,然后是所有行来自dataset_2

如果我们使用 hstack ,我们将数据集“一个挨着一个”堆叠,这意味着第一行dataset_1的元素后面将是第一行dataset_2的元素。这将应用于每一行:

np.vstack([dataset_1, dataset_2])        # combine datasets vertically
np.hstack([dataset_1, dataset_2])        # combine datasets horizontally
np.stack([dataset_1, dataset_2], axis=0) # combine datasets on axis 0

注意

结合堆叠可能需要一些时间来适应。更多信息请查看 NumPy 文档中的示例:https://docs . scipy . org/doc/NumPy-1 . 15 . 0/reference/generated/NumPy . hstack . html .

重塑

重塑对于某些算法来说至关重要。根据数据的性质,它可能有助于您降低维度,使可视化更容易:

dataset.reshape(-1, 2)       # reshape dataset to two columns x rows
np.reshape(dataset, (1, -1)) # reshape dataset to one row x columns

这里,-1是 NumPy 自动识别的未知维度。NumPy 将首先计算出任何给定数组的长度和剩余维度,从而确保它满足给定的上述标准。

活动 3:过滤、排序、组合和整形

NumPy 的最后一个活动提供了一些更复杂的任务来巩固我们的学习。它还将结合大部分以前学过的方法作为总结。请执行以下步骤:

  1. Lesson01文件夹打开activity03.ipynb Jupyter 笔记本,执行本活动。

  2. NumPy 将是此活动唯一必需的依赖项,因此请确保导入它。

  3. 再次,使用 NumPy 加载normal_distribution.csv数据集。通过查看数据库,确保一切正常。

  4. 加载数据集后,使用前面讨论的过滤功能过滤大于 105 的值,过滤介于 90 到 95 之间的值,并获取差值小于 1 到 100 的值的索引。

  5. 当试图在序数数据类型列上显示数据时,对数据进行排序是一项重要功能。使用排序对每行的值进行排序,对每列的值进行排序,得到每行的位置索引,得到每行的 3 个最小值(其余值不排序)。

  6. 使用本章练习 02 中的拆分数据,我们还可以使用前面讨论的组合特征将第一列的后半部分重新添加在一起,将第二列添加到我们的组合数据集,并将第三列添加到我们的组合数据集。

  7. Use the reshaping features to reshape the dataset in a one-dimensional list with all the values and reshape the dataset into a matrix with only two columns.

    注意:

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

接下来,我们将学习熊猫,这将在处理比简单的多维数字数据更复杂的数据时带来几个优势。pandas 还支持数据集中不同的数据类型,这意味着我们可以有保存字符串的列和其他有数字的列。

正如你所看到的,NumPy 本身有一些非常强大的工具。它们中的一些与熊猫结合时甚至更强大。

熊猫

pandsPython 库提供了数据结构和方法来处理不同类型的数据,例如数值和时间数据。这些操作易于使用,并且针对性能进行了高度优化。

数据格式如 CSVJSON 和数据库可用于数据框的创建。数据帧是数据的内部表示,与表非常相似,但功能更强大。导入和读取文件和内存中的数据被抽象成一个用户友好的界面。在处理丢失的数据时,pandas 提供了内置的解决方案来清理和扩充您的数据,这意味着它会用合理的值来填充丢失的值。

集成的索引和基于标签的切片结合花哨的索引(我们已经在 NumPy 上看到的)使得处理数据变得简单。更复杂的技术,如重塑旋转、融合数据,以及轻松连接合并数据的可能性,为正确处理您的数据提供了强大的工具。

如果你正在处理时间序列数据,诸如日期范围生成频率转换移动窗口统计等操作可以为你的扯皮提供一个高级界面。

注意

熊猫的安装说明可以在这里找到:https://pandas.pydata.org/

大熊猫相对于 NumPy 的优势

以下是熊猫的一些优点:

  • 高抽象层次 : pandas 比 NumPy 有更高的抽象层次,给了用户更简单的交互界面。它抽象出一些更复杂的概念,使其更容易使用和理解。
  • 少一点直觉:很多方法,比如加入、选择、加载文件,都是可以用的,没有太多的直觉,也没有带走熊猫很多强大的天性。
  • 更快的处理:数据帧的内部表示允许对某些操作进行更快的处理。当然,这总是取决于数据及其结构。
  • 简单的数据框设计:数据框是为大型数据集的操作而设计的。

熊猫的劣势

以下是熊猫的一些缺点:

  • 不太适用:由于抽象程度较高,一般比 NumPy 不太适用。尤其是当超出其使用范围时,操作很快变得非常复杂和困难。

  • 更多的磁盘空间:由于数据帧的内部表示和熊猫为了更高性能的执行而交换磁盘空间的方式,复杂操作的内存使用可能会激增。

  • 性能问题:特别是在执行不推荐的重连接时,内存使用可能会变得很关键,并可能导致性能问题。

  • Hidden complexity: The comparatively simple interface has its downsides too. Less experienced users often tend to overuse methods and execute them several times instead of reusing what they've already calculated. This hidden complexity makes users think that the operations themselves are simple, which is not the case.

    注意

    始终尝试思考如何设计您的工作流,而不是过度使用操作。

练习 2:加载样本数据集并计算平均值

在本练习中,我们将加载world_population.csv数据集,并计算一些行和列的平均值。我们的数据集保存了每个国家的年人口密度。让我们用熊猫来获得一些快速简单的见解:

  1. Lesson01文件夹打开exercise02.ipynb Jupyter 笔记本,执行本练习。

  2. 导入熊猫库:

    # importing the necessary dependencies
    import pandas as pd
    
  3. 导入熊猫后,我们可以使用read_csv方法加载上述数据集。我们希望使用包含国家名称的第一列作为我们的索引。我们将使用index_col参数。完整的一行应该是这样的:

    # loading the dataset
    dataset = pd.read_csv('./data/world_population.csv', index_col=0)
    
  4. As before, have a look at the data you just imported by simply writing the name of the dataset in the next cell. pandas uses a data structure called DataFrames. We want to only print some of the rows to avoid filling the screen. pandas DataFrames come with two methods, head() and tail(), to do this for you. Both take number, n, as a parameter, which describes how many rows should be returned:

    注意

    简单地执行一个返回值的单元格,如数据框,将使用 Jupyter 格式,这看起来更好,在大多数情况下,比使用print显示更多的信息。

    # looking at the dataset
    dataset.head()
    

    前面代码的输出如下:

    Figure 1.14: The first five rows of our dataset

    图 1.14:数据集的前五行
  5. To get a quick overview of our dataset, we want to print out its shape.

    这将以(rows, columns)的形式给出输出。使用dataset.shape命令打印出形状。这与 NumPy ndarrays 的工作原理完全相同:

    # printing the shape of our dataset
    dataset.shape
    

    前面代码的输出如下:

    Figure 1.15: The shape of the dataset

    图 1.15:数据集的形状
  6. Calculating the mean is the next step, once we've loaded and checked our dataset. Indexing rows work a little bit differently and we'll look at this in detail in the activities that follow. For now, we only want to index the column with the year 1961.

    熊猫数据帧有内置的计算功能,如mean。这意味着我们可以简单地调用dataset.mean()来获得结果。

    打印输出应该如下所示:

    # calculating the mean for 1961 column
    dataset["1961"].mean()
    

    前面代码的输出如下:

    Figure 1.16: Mean of elements in the 1961 column

    图 1.16:1961 年栏中元素的平均值
  7. Just to see the difference in population density over the years, we'll do the same with the column for the year 2015 (the population more than doubled in the given time range):

    # calculating the mean for 2015 column
    dataset["2015"].mean()
    

    前面代码的输出如下:

    Figure 1.17: Mean of elements in the 2015 column

    图 1.17:2015 年栏中元素的平均值
  8. If we want to get the mean for every single country (row), we can make use of pandas axis tools. By simply passing the axis parameter in the dataset.mean() call, we define the dimension our data will be aggregated on.

    axis=0是水平的(每列)axis=1是垂直的(每行),所以如果我们想得到每行的结果,我们需要选择axis=1。由于我们的数据集中有 264 行,我们希望将返回国家的数量限制在 10 个。如前所述,我们可以使用参数为 10 的head(10)tail(10)方法:

    # mean for each country (row)
    dataset.mean(axis=1).head(10)
    

    前面代码的输出如下:

    Figure 1.18: Mean of elements in the first 10 countries (rows)

    图 1.18:前 10 个国家(行)元素的平均值

    下面是 tail 方法的代码:

    # mean for each feature (col)
    dataset.mean(axis=0).tail(10)
    

    前面代码的输出如下:

    Figure 1.19: Mean of elements for the last 10 years (columns)

    图 1.19:过去 10 年元素平均值(列)
  9. The last task of this exercise is to calculate the mean of the whole DataFrame. Since pandas DataFrames can have different datatypes in each column, aggregating this value on the whole dataset out of the box makes no sense. By default, axis=0 will be used, which means that this will give us the same result as the cell before:

    # calculating the mean for the whole matrix
    dataset.mean()
    

    前面代码的输出如下:

    Figure 1.20: Mean of elements for each column

图 1.20:每列元素的平均值

恭喜你!我们现在已经看到熊猫的界面有一些类似于 NumPy 的方法,这使得它真的很容易理解。我们现在已经介绍了最基本的内容,这将帮助你解决第一个使用熊猫的活动。在下面的活动中,你将巩固你对熊猫的基本知识,并使用你刚刚学习的方法来解决几个计算任务。

活动 4:用熊猫计算给定数字的平均值、中位数和方差

在本活动中,我们将利用之前学习的导入数据集和进行一些基本计算的技能,并应用它们来解决我们使用熊猫的第一个活动的任务。

我们想巩固我们的新技能,熟悉熊猫:

  1. Lesson01文件夹中打开 Jupyter 笔记本activity04.ipynb执行本活动。

  2. 由于我们现在正在和熊猫一起工作,我们必须在活动开始时导入它,以便能够在笔记本中使用它。

  3. 使用熊猫的read_csv方法加载world_population.csv数据集。

  4. 给定数据集,进入并使用熊猫的内置方法计算第三行、最后一行和国家德国的平均值。

  5. 然后,计算最后一行、最后 3 行和前 10 个国家的中位数。

  6. Last, calculate the variance of the last 5 columns.

    注意:

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

恭喜你!您已经完成了与熊猫的第一次活动,这向您展示了与 NumPy 和熊猫合作时的一些相似之处和不同之处。在接下来的活动中,这些知识将得到巩固。你还将被介绍熊猫更复杂的特征和方法。

熊猫的基本操作

在这一节中,我们将学习基本的熊猫操作,如索引、切片和迭代,并通过一个活动来实现它。

索引

用熊猫做索引比用 NumPy 要复杂一点。我们只能访问带有单个括号的列。要使用行的索引来访问它们,我们需要iloc方法。如果我们想通过index_col(在read_csv调用中设置)访问它们,我们需要使用loc方法:

dataset["2000"]                    # index the 2000 col
dataset.iloc[-1]                   # index the last row
dataset.loc["Germany"]             # index the row with index Germany
dataset[["2015"]].loc[["Germany"]] # index row Germany and column 2015

切片

用熊猫切片更有力量。我们可以使用我们已经在 NumPy 中看到的默认切片语法,或者使用多重选择。如果我们想按名称划分不同的行或列,我们可以简单地在括号中传递一个列表:

dataset.iloc[0:10]                 # slice of the first 10 rows
dataset.loc[["Germany", "India"]]  # slice of rows Germany and India
# subset of Germany and India with years 1970/90
dataset.loc[["Germany", "India"]][["1970", "1990"]] 

迭代

迭代数据帧也是可能的。考虑到它们可以有几个维度和数据类型,索引是非常高级的,并且必须分别对每一行进行迭代:

# iterating the whole dataset
for index, row in dataset.iterrows():
    print(index, row)

系列

熊猫系列是一维标签阵列,能够保存任何类型的数据。我们可以通过从. csv 文件、Excel 电子表格或 SQL 数据库中加载数据集来创建系列。有许多不同的方法来创建它们。例如:

  • NumPy 数组:

    # import pandas
    import pandas as pd
    # import numpy
    import numpy as np
    # creating a numpy array
    numarr = np.array(['p','y','t','h','o','n'])
    ser = pd.Series(numarr)
    print(ser)
    
  • 熊猫名单:

    # import pandas
    import pandas as pd
    # creating a pandas list
    plist = ['p','y','t','h','o','n']
    ser = pd.Series(plist)
    print(ser)
    

活动 5:使用熊猫进行索引、切片和迭代

在本练习中,我们将使用前面讨论过的熊猫特征,使用熊猫系列对数据帧进行索引、切片和迭代。为了深入了解数据集,我们需要能够对数据进行显式索引、切片和迭代。例如,我们可以比较几个国家的人口密度增长。

在查看了不同的操作之后,我们想要显示德国、新加坡、美国和印度在 1970 年、1990 年和 2010 年的人口密度:

  1. Lesson01文件夹打开activity05.ipynb Jupyter 笔记本,执行本活动。

  2. 在加载数据集之前,我们需要导入熊猫。我们将再次使用pd别名来指代熊猫。

  3. 使用熊猫加载world_population.csv数据集。查看数据帧,确保一切正常。

  4. 加载数据集后,使用前面讨论的索引功能为美国的行、第二行到最后一行、2000 年的列(系列)和 2000 年的印度人口密度编制索引。

  5. 创建数据集的子列表需要切片。使用此概念将第 2 行到第 5 行中的国家分割开来,将德国、新加坡、美国和印度分割开来,并将德国、新加坡、美国和印度分割成 1970 年、1990 年和 2010 年的人口密度。

  6. The last task in this activity will be iterating over the first three countries in our dataset. Use the previously discussed iteration method to iterate over the whole dataset and print the name, country code, and population for the years 1970, 1990, and 2010.

    注意:

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

恭喜你!我们已经介绍了大多数使用熊猫的基本数据争论方法。在下一个活动中,我们将了解更高级的功能,如过滤、排序和整形,为下一章做准备。

先进熊猫行动

在本节中,我们将学习先进的熊猫操作,如过滤、排序和整形,并在活动中实现它们。

过滤

熊猫中的过滤比 NumPy 有更高级别的界面。你仍然可以使用基于“简单”括号的条件过滤。但是,您也可以使用更复杂的查询,例如,基于正则表达式筛选行:

dataset.filter(items=["1990"])      # only column 1994
dataset[(dataset["1990"] < 10)]     # countries'population density < 10 in 1999
dataset.filter(like="8", axis=1)    # years containing an 8
dataset.filter(regex="a$", axis=0)  # countries ending with a

排序

根据给定的行或列对每一行或每一列进行排序将有助于您更好地了解数据,并找到给定数据集的排名。有了熊猫,我们可以很容易地做到这一点。可以使用名为升序的参数进行升序和降序排序。当然,您可以通过在by = [ ]列表中提供多个值来进行更复杂的排序。然后,这些将用于对第一个值相同的值进行排序:

dataset.sort_values(by=["1999"])        # values sorted by 1999
# values sorted by 1999 descending
dataset.sort_values(by=["1994"], ascending=False)

重塑

重塑对于简化可视化和算法至关重要。但是,根据您的数据,这可能会变得非常复杂:

dataset.pivot(index=["1999"] * len(dataset), columns="Country Code", values="1999")

注意

重塑是一个非常复杂的话题。如果你想深入其中,这是一个很好的入门资源:https://bit.ly/2SjWzaB

活动 6:过滤、排序和整形

熊猫的最后一项活动提供了一些更复杂的任务,也结合了以前学到的大部分方法作为总结。完成这项活动后,学生应该能够阅读最基本的熊猫代码,并理解其逻辑:

  1. Lesson01文件夹打开activity06.ipynb Jupyter 笔记本,执行本活动。有关如何在 JupyterLab 中打开 Jupyter 笔记本的说明,请参考本章的介绍。

  2. 过滤、排序和整形都是熊猫的方法。在我们使用它们之前,我们必须进口熊猫。

  3. Again, load the world_population.csv dataset using pandas and make sure everything works by having a look at the DataFrames:

    Figure 1.21: Looking at the first two columns of our dataset

    图 1.21:查看数据集的前两列
  4. 加载数据集后,使用前面讨论的过滤功能,以列的形式获取 1961 年、2000 年和 2015 年,以及 2000 年人口密度大于 500 的所有国家。此外,使用过滤获得一个新的数据集,该数据集只包含 2000 年及以后的年份、以 A 开头的国家以及包含“土地”一词的国家

  5. 当试图在序数数据类型列上显示数据时,对数据进行排序是一项重要功能。使用排序操作按 1961 年的升序、2015 年的升序和 2015 年的降序对值进行排序。

  6. If our input dataset does not suit our needs, we have to reshape it. Use the reshaping features to reshape the dataset to 2015 as row and country codes as columns.

    注意:

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

你现在已经完成了关于熊猫的主题,这一章到此结束。我们已经了解了帮助您争论和处理数据的基本工具。熊猫本身是一个令人难以置信的强大和大量使用的数据争论工具。

总结

NumPy 和熊猫是数据角力的必备工具。它们的用户友好的界面和高性能的实现使数据处理变得容易。尽管它们只对我们的数据集提供了一点点洞察,但它们对于争论、扩充和清理我们的数据集绝对有价值。掌握这些技能将会提高你的可视化质量。

在这一章中,我们学习了 NumPy 和熊猫的基础知识,以及统计学的概念。尽管所涵盖的统计概念非常基本,但它们对于丰富我们的可视化是必要的,因为在大多数情况下,我们的数据集并不直接提供这些信息。这种实践经验将帮助您实施以下章节中的练习和活动。

在下一章中,我们将关注不同类型的可视化,以及如何决定哪种可视化最适合您的情况。这将为您提供理论知识,以便您知道何时使用特定的图表类型以及原因。它还将奠定章节的基础,重点是教你如何使用 Matplotlib 和 seaborn 来创建所讨论的绘图。在我们用 Matplotlib 和 seaborn 介绍了基本的可视化技术之后,我们将深入探讨交互式和动画图表的可能性,这将在我们的可视化中引入讲故事的元素。

二、你需要了解的所有绘图

学习目标

本章结束时,您将能够:

  • 确定给定数据集和场景的最佳绘图类型
  • 解释某些绘图的设计实践
  • 设计出色的有形可视化效果

在本章中,我们将学习不同类型绘图的基础知识。

简介

在本章中,我们将关注各种可视化,并确定哪种可视化最适合显示给定数据集的特定信息。我们将详细描述每一个可视化,并给出实际的例子,例如随着时间的推移比较不同的股票,或者比较不同电影的收视率。从比较图开始,它非常适合于比较多个变量随时间的变化,我们将看看它们的类型,如折线图、条形图和雷达图。关系图便于显示变量之间的关系。我们将介绍显示两个变量之间关系的散点图、三个变量的气泡图、变量对的相关图,最后是热图。

下面将解释组合图,它用于可视化作为整体一部分的变量,以及饼图、堆叠条形图、堆叠面积图和维恩图。为了更深入地了解变量的分布,使用了分布图。作为分布图的一部分,直方图、密度图、箱线图和小提琴图将被涵盖。最后,我们将讨论点地图、连接地图和点地图,它们可以分为地理图。地理图对于可视化地理空间数据非常有用。

对比图

比较图包括非常适合比较多个变量或变量随时间变化的图表。对于项目之间的比较,条形图(也称为柱形图)是最好的方法。折线图非常适合可视化随时间变化的变量。对于某个时间段(比如说,少于十个时间点),也可以使用垂直条形图。雷达图或蜘蛛图非常适合可视化多个组的多个变量。

折线图

折线图用于显示连续时间段内的定量值,并将信息显示为一系列。折线图非常适合由直线段连接的时间序列。

该值位于 y 轴上,而 x 轴是时间刻度。

用途:

  • 折线图非常适合比较多个变量以及可视化单个和多个变量的趋势,尤其是如果数据集有多个时间段(大约超过十个)。
  • 对于较小的时间段,垂直条形图可能是更好的选择。

下图显示了 20 年来房地产价格(百万美元)的趋势。折线图非常适合显示数据趋势:

Figure 2.1: Line chart for a single variable

图 2.1:单一变量的折线图

:

下图是一个多变量折线图,比较了谷歌、脸书、苹果、亚马逊和微软的股票收盘价。折线图对于比较价值和可视化股票趋势非常有用。正如我们所看到的,亚马逊显示了最高的增长:

Figure 2.2: Line chart showing stock trends for the five companies

图 2.2:显示五家公司股票趋势的折线图

设计实践:

  • 避免每个图表有太多线条

  • Adjust your scale so that the trend is clearly visible

    注意

    多变量绘图的设计实践。应该有一个图例来描述每个变量。

条形图

条形长度对值进行编码。条形图有两种变体:垂直条形图和水平条形图。

用途:

  • 虽然它们都用于比较不同类别的数值,但垂直条形图有时也用于显示一段时间内的单个变量。

条形图的注意事项和不注意事项:

  • 不要混淆垂直条形图和直方图。条形图比较不同的变量或类别,而直方图显示单个变量的分布。直方图将在本章后面讨论。
  • 另一个常见的错误是使用条形图来显示组或类别之间的中心趋势。在这些情况下,使用箱线图或小提琴图来显示统计度量或分布。

示例:

下图显示了一个垂直条形图。每个条形显示了五名学生在测试中获得的 100 分中的分数:

Figure 2.3: Vertical bar chart using student test data

图 2.3:使用学生测试数据的垂直条形图

下图显示了一个水平条形图。每个条形显示了五名学生在测试中获得的 100 分中的分数:

Figure 2.4: Horizontal bar chart using student test data

图 2.4:使用学生测试数据的水平条形图

下图比较了电影分级,给出了两个不同的分数。Tomatometer 是对这部电影给予正面评价的认可影评人的百分比。受众分数是 5 分中给出 3.5 分或更高分数的用户的百分比。如我们所见,《火星人》是唯一一部同时获得高分和观众评分的电影。《霍比特人:意外之旅》的观众评分相对于 Tomatometer 的评分相对较高,这可能是由于庞大的粉丝群:

Figure 2.5: Comparative bar chart

图 2.5:比较条形图

设计实践:

  • 对应于数值变量的轴应该从零开始。从另一个值开始可能会产生误导,因为它会使小的值差异看起来很大。
  • 使用水平标签,也就是说,只要横条的数量少,图表看起来不会太杂乱。

雷达图

雷达图,也称为蜘蛛网络图,将多个变量可视化,每个变量绘制在自己的轴上,形成一个多边形。所有轴都是径向排列的,从中心开始,彼此之间的距离相等,并且具有相同的刻度。

用途:

  • 雷达图非常适合比较单个组或多个组的多个定量变量。
  • 它们对于显示数据集中哪些变量得分高或低也很有用,是可视化性能的理想选择

示例:

下图显示了单个变量的雷达图。此图表显示学生在不同科目中的分数数据:

Figure 2.6: Radar chart for one variable (student)

图 2.6:一个变量(学生)的雷达图

下图显示了两个变量/组的雷达图。在这里,图表解释了两个学生在不同科目上的分数:

Figure 2.7: Radar chart for two variables (two students)

图 2.7:两个变量(两个学生)的雷达图

下图显示了多个变量/组的雷达图。每个图表显示学生在不同科目中的表现数据:

Figure 2.8: Radar chart with faceting for multiple variables (multiple subjects)

图 2.8:多变量(多主题)面阵雷达图

设计实践:

  • 尽量在一张雷达图上显示十个或更少的因子,以便于阅读。
  • 如上图所示,对多个变量/组使用刻面,以保持清晰。

活动 7:员工技能比较

你会得到四名员工(甲、乙、丙和丁)的五个属性的分数:效率、质量、承诺、负责任的行为和合作。您的任务是比较员工及其技能:

  1. 哪些图表适合这项任务?

  2. You are given the following bar and radar charts. List the advantages and disadvantages for both charts. Which is the better chart for this task in your opinion and why?

    Figure 2.9: Employee skills comparison with a bar chart

    图 2.9:员工技能与条形图的比较

    下图显示了员工技能的雷达图:

    Figure 2.10: Employee skills comparison with a radar chart

    图 2.10:员工技能与雷达图的比较
  3. What could be improved in the respective visualizations?

    注意:

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

关系图

关系图非常适合显示变量之间的关系。散点图显示了一个或多个组的两个变量之间的相关性。气泡图可以用来显示三个变量之间的关系。附加的第三个变量由点大小表示。热图对于揭示两个定性变量之间的模式或关联非常有用。相关图是显示多个变量之间相关性的完美可视化。

散点图

散点图显示两个数值变量的数据点,在两个轴上显示一个变量。

用途:

  • 您可以检测两个变量之间是否存在相关性(关系)。
  • 它们允许您使用不同的颜色绘制多个组或类别的关系。
  • 气泡图是散点图的变体,是可视化第三个变量相关性的绝佳工具。

示例:

下图显示了属于单个群体的人的身高体重的散点图:

Figure 2.11: Scatter plot with a single variable (one group)

图 2.11:单变量散点图(一组)

下图显示了与上图相同的数据,但不同组之间有所不同。在这种情况下,我们有不同的组: ABC :

Figure 2.12: Scatter plot with multiple variables (three groups)

图 2.12:多变量散点图(三组)

下图显示了不同种类动物的体重和最大寿命之间的关系。体重和最大寿命之间呈正相关:

Figure 2.13: Correlation between body mass and maximum longevity for animals

图 2.13:动物体重和最大寿命之间的相关性

设计实践:

  • 从零开始两个轴,以准确表示数据。
  • 对数据点使用对比色,避免对具有多个组或类别的散点图使用符号。

变量 : 带有边缘直方图的散点图

除了散点图显示两个数值变量之间的相关性之外,您还可以以直方图的形式绘制每个变量的边际分布,以便更好地了解每个变量的分布情况。

示例:

下图显示了鸟类动物的体重和最大寿命之间的相关性。还显示了边缘直方图,这有助于更好地了解这两个变量:

Figure 2.14: Correlation between body mass and maximum longevity of the Aves class with marginal histograms

图 2.14:体重和边缘直方图 Aves 类最大寿命之间的相关性

气泡图

气泡图通过引入第三个数值变量扩展了散点图。变量值由点的大小表示。圆点的面积与数值成正比。图例用于将点的大小与实际数值联系起来。

用途:

  • 显示三个变量之间的相关性。

:

下图显示了突出人类身高和年龄之间关系的气泡图:

Figure 2.15: Bubble plot showing relation between height and age of humans

图 2.15:显示人类身高和年龄关系的气泡图

设计实践:

  • 散点图的设计实践也适用于气泡图。
  • 不要将它用于非常大量的数据,因为太多的气泡会使图表难以阅读。

相关图

相关图是散点图和直方图的组合。本章稍后将详细讨论直方图。相关图或相关矩阵使用散点图可视化每对数值变量之间的关系。

相关矩阵的对角线以直方图的形式表示每个变量的分布。您还可以使用不同的颜色绘制多个组或类别的关系。相关图是探索性数据分析的一个很好的图表,可以让你对数据有所感觉,尤其是变量对之间的相关性。

示例:

下图显示了人类身高、体重和年龄的相关图。对角线图显示了每个变量的直方图。非对角线元素显示变量对之间的散点图:

图 2.16:单一类别相关图

下图显示了按颜色将数据样本分成不同组的相关图:

Figure 2.17: Correlogram with multiple categories

图 2.17:多类别相关图

设计实践:

  • 从零开始两个轴,以准确表示数据。
  • 对数据点使用对比色,避免对具有多个组或类别的散点图使用符号。

热图

热图是一种可视化,其中包含在矩阵中的值被表示为颜色或颜色饱和度。热图非常适合可视化多元数据,其中分类变量位于行和列中,数字或分类变量表示为颜色或颜色饱和度。

用途:

  • 多元数据的可视化。非常适合在数据中查找模式。

示例:

下图显示了各种电子商务网站电子产品类别页面上最受欢迎产品的热图:

Figure 2.18: Heatmap for popular products in the Electronics category

图 2.18:电子产品类热门产品的热图

变体 : 标注热图

让我们看看之前在带标注的热图中看到的相同示例:

Figure 2.19: Annotated heatmap for popular products in the Electronics category

图 2.19:电子产品类别中受欢迎产品的标注热图

活动 8:二十年来发生的道路事故

您将看到一张图表,其中提供了过去二十年中一月、四月、七月和十月发生的道路事故的相关信息:

  1. 确定道路事故发生次数最少的年份。
  2. 在过去的二十年中,找出事故明显减少的月份:

Figure 2.20: Total accidents over 20 years

图 2.20:20 年间的事故总数

注意:

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

组成图

构图图如果你把某个事物看成是整体的一部分,那么构图图是理想的。对于静态数据,可以使用饼图、堆积条形图或维恩图。饼图甜甜圈图有助于显示各组的比例和百分比。如果你需要一个额外的维度,堆叠条形图是很好的。维恩图是可视化重叠组的最佳方式,其中每个组由一个圆圈表示。对于随时间变化的数据,可以使用堆积条形图或堆积面积图。

饼图

饼状图通过将一个圆分割成薄片来说明数值比例。每个弧长代表一个类别的一个比例。整圆等于 100%。对于人类来说,比较条比弧长更容易;因此,建议大部分时间使用条形图或堆叠条形图。

用途:

  • 比较作为整体一部分的项目。

示例:

下图显示了一个饼图,显示了板球场地的不同防守位置,如长开、长关、第三人和细腿:

Figure 2.21: Pie chart showing fielding positions in a cricket ground

图 2.21:显示板球场上防守位置的饼图

下图显示了世界各地的用水量:

Figure 2.22: Pie chart for global water usage

图 2.22:全球用水量饼图

设计实践:

  • 按照切片大小的递增/递减顺序排列切片,顺时针或逆时针排列。
  • 确保每个切片都有不同的颜色。

变体 : 甜甜圈图

饼图的另一种选择是环形图。与饼图相比,比较切片的大小更容易,因为读者更关注于阅读弧线的长度,而不是面积。圆环图也更节省空间,因为中心被切掉了,所以它可以用来显示信息或进一步将组分成子组。

下图显示了一个基本的圆环图:

Figure 2.23: Donut chart

图 2.23:圆环图

下图显示了包含子组的圆环图:

Figure 2.24: Donut chart with subgroups

图 2.24:包含子组的圆环图

设计实践:

  • 子类别使用相同的颜色(用于类别)。对不同的子类别使用不同的亮度级别。

堆叠条形图

堆叠条形图用于显示一个类别如何划分为子类别,以及子类别相对于整体类别的比例。您可以比较每个条形的总金额,也可以显示每个组的百分比。后者也被称为 100%堆积条形图,可以更容易地看到各组数量之间的相对差异。

用途:

  • 比较可分为子变量的变量。

示例:

下图显示了包含五个组的通用堆积条形图:

Figure 2.25: Stacked bar chart to show sales of laptops and mobiles

图 2.25:显示笔记本电脑和手机销量的堆积条形图

下图显示了 100%堆叠条形图,其数据与上图中使用的数据相同:

Figure 2.26: 100% stacked bar chart to show sales of laptops, PCs, and mobiles

图 2.26: 100%堆叠条形图,显示笔记本电脑、个人电脑和手机的销售情况

下图说明了一家餐馆几天的每日总销售额。不吸烟者的每日总销售额叠加在吸烟者的每日总销售额之上:

Figure 2.27: Daily total sales of restaurant categorized by smokers and non-smokers

图 2.27:按吸烟者和不吸烟者分类的餐馆每日总销售额

设计实践:

  • 对堆叠的条形使用对比色。
  • 确保横条有足够的间距,以消除视觉混乱。每个条形之间的理想间距是条形宽度的一半。
  • 按字母顺序、顺序或值对数据进行分类,以便统一排序,让您的受众更容易理解。

堆叠面积图

堆叠面积图显示了部分-整体关系的趋势。几个组的值相互叠加。它有助于分析个人和整体趋势信息。

用途:

  • 显示作为整体一部分的时间序列趋势。

示例:

下图显示了谷歌、脸书、推特和 Snapchat 等公司过去十年的净利润堆积面积图:

Figure 2.28: Stacked area chart to show net profits of four companies

图 2.28:显示四家公司净利润的堆叠面积图

设计实践:

  • 使用透明颜色可能会提高信息的可见性。

活动 9:智能手机销售单位

您想比较五大智能手机制造商的智能手机销量,看看是否有任何趋势:

  1. 看下面的折线图,分析每个制造商的销售情况,找出与第三季度相比,第四季度表现优异的制造商。
  2. 分析所有厂商的业绩,对销量单位将呈现下降趋势和上升趋势的两家公司进行预测:

图 2.29:智能手机销售单位折线图

注意:

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

文氏图

文氏图,也称为集合图,显示不同集合的有限集合之间所有可能的逻辑关系。每一组用一个圆来表示。圆圈的大小说明了一个群体的重要性。重叠的大小表示多个组之间的交集。

用途:

  • 要显示不同集合的重叠

:

  • 将下图的交叉点可视化显示了一个学期中选修同一门课的两组学生的维恩图:

Figure 2.30: Venn diagram to show students taking the same class

图 2.30:显示学生上同一堂课的文氏图

设计实践:

  • 如果您有三个以上的组,不建议使用维恩图。这将变得难以理解。

分布图

分布图深入了解数据是如何分布的。对于单个变量,直方图非常适合。对于多个变量,您可以使用方框图或小提琴图。小提琴图可视化了变量的密度,而箱线图仅可视化了每个变量的中位数、四分位数范围和范围。

直方图

一个直方图可视化了单个数值变量的分布。每个小节代表特定时间间隔的频率。直方图有助于估计统计度量。您可以看到值集中的地方,并且可以轻松检测异常值。您可以用绝对频率值绘制直方图,也可以归一化直方图。如果你想比较多个变量的分布,你可以使用不同的颜色。

用途:

  • 深入了解数据集的底层分布

:

下图显示了测试组的智商分布。实线表示平均值,虚线表示标准偏差:

Figure 2.31: Distribution of Intelligence Quotient (IQ) for a test group of a hundred adults

图 2.31:由一百名成年人组成的测试组的智商分布

设计实践:

  • 尝试不同数量的箱,因为直方图的形状可能会有很大的不同。

密度图

一个密度图显示了一个数值变量的分布。它是直方图的变体,使用核平滑,允许更平滑的分布。与直方图相比,它们的一个优点是密度图在确定分布形状方面更好,因为直方图的分布形状在很大程度上取决于面元的数量(数据间隔)。

用途:

  • 您可以通过在同一轴上绘制密度并使用不同的颜色来比较几个变量的分布。

:

下图显示了基本密度图:

Figure 2.32: Density plot

图 2.32:密度图

下图显示了基本的多密度图:

Figure 2.33: Multi-density plot

图 2.33:多密度图

设计实践:

  • 使用对比色绘制多个变量的密度。

箱线图

方框图显示了多个统计测量值。该框从数据的下四分位数延伸到上四分位数,从而允许我们可视化四分位数之间的范围。方框内的水平线表示中间值。从盒子里伸出的显示了数据的范围。它也是一个显示数据异常值的选项,通常是圆形或菱形,超过胡须的末端。

用途:

  • 如果要比较多个变量或组的统计度量,可以简单地将多个框一个接一个地绘制出来。

示例:

下图显示了一个基本的方框图:

Figure 2.34: Box plot showing single variable

图 2.34:显示单变量的箱线图

下图显示了多个变量的基本方框图:

Figure 2.35: Box plot for multiple variables

图 2.35:多个变量的箱线图

小提琴绘图

小提琴图是方块图和密度图的组合。统计测量和分布都是可视化的。中间粗黑条代表四分位数区间,细黑线表示 95%置信区间,白点表示中位数。在中心线的两侧,密度是可视化的。

用途:

  • 如果您想要比较多个变量或组的统计度量,您可以简单地将多个小提琴一个接一个地绘制出来。

示例:

下图显示了单个变量的小提琴图,并显示了学生在数学中的表现:

Figure 2.37: Violin plot for a single variable (Math)

图 2.36:单一变量的小提琴图(数学)

下图显示了两个变量的小提琴图,并显示了学生在英语数学中的表现:

Figure 2.37: Violin plot for multiple variables (English and math)

图 2.37:多个变量(英语和数学)的小提琴图

下图显示了分成三组的单个变量的小提琴图,并显示了三组学生在英语中的表现:

Figure 2.38: Violin plot with multiple categories (three groups of students)

图 2.38:多类别小提琴绘图(三组学生)

设计实践:

  • 相应地缩放轴,使分布清晰可见且不平坦。

活动 10:不同时间间隔的列车频率

为您提供了一个直方图,说明不同时间间隔到达的列车总数:

  1. 通过看下面的图表,你能确定最多列车到达的时间间隔吗?

  2. How would the histogram change if the number of trains arriving between 4 and 6 pm were to be increased by 50?

    Figure 2.39: Frequency of trains during different time intervals

图 2.39:不同时间间隔的列车频率

注意:

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

地理绘图

地质图是可视化地理空间数据的好方法。Choropleth 地图可用于比较不同国家、州等的定量值。如果你想显示不同位置之间的连接,连接图是必经之路。

点图

点图中,每个点代表一定数量的观察值。每个点都有相同的大小和值(每个点代表的观察次数)。这些点不是用来计数的——它们只是用来给人一种数量级的印象。大小和价值是可视化效果和印象的重要因素。您可以为点使用不同的颜色或符号来显示多个类别或组。

用途:

  • 为了地理空间数据的可视化

:

下图显示了一个点图,其中每个点代表全世界一定数量的公交站点:

Figure 2.40: Dot map showing bus stops worldwide

图 2.40:显示全球公交站点的点地图

设计实践:

  • 不要显示太多位置。您应该仍然能够看到地图,以获得实际位置的感觉。
  • 选择一个点的大小和值,以便在密集的区域,点开始混合。点地图应该给人一个良好的印象,潜在的空间分布。

氯普勒斯地图

弦贴图中,每个图块都被着色以编码一个变量。图块代表地理区域,例如县和国家。Choropleth 地图提供了一个很好的方法来显示一个变量如何在一个地理区域内变化。对于 choropleth 地图,需要记住的一点是,人眼自然会对更大的区域给予更多的关注,因此您可能希望通过按区域划分地图来标准化您的数据。

用途:

  • 用于地理空间数据的可视化,这些地理空间数据按地理区域分组,例如州或国家

:

下图显示了美国天气预报的地图:

Figure 2.41: Choropleth map showing weather forecast of USA

图 2.41:显示美国天气预报的地图

设计实践:

  • 使用较深的颜色表示较高的值,因为它们被认为是较高的量级。
  • 限制颜色的层次,因为人眼只能分辨出多少种颜色。七个色阶应该足够了。

连接图

连接图中,每条线代表两个位置之间的一定数量的连接。位置之间的链接可以用代表它们之间最短距离的直线或圆角来绘制。

每条线都具有相同的厚度和值(每条线代表的连接数)。这些线不是用来计数的;它们只是为了给人一种巨大的印象。连接线的大小和值是可视化效果和印象的重要因素。

您可以为线条使用不同的颜色来显示多个类别或组,也可以使用颜色映射来编码连接的长度。

用途:

  • 为了连接的可视化

示例:

下图显示了世界各地航班连接的连接图:

Figure 2.42: Connection map showing Flight connections around the world

图 2.42:显示全球航班连接的连接图

设计实践:

  • 不要显示太多的连接。您仍然应该查看地图,以了解起点和终点的实际位置。
  • 选择线条粗细和值,以便线条开始在密集区域混合。连接图应该给出底层空间分布的良好印象。

什么是好的可视化?

一个好的可视化有多个方面:

  • 最重要的是,可视化应该是不言自明的,视觉上有吸引力的。为了使其不言自明,请使用图例、x 轴和 y 轴的描述性标签以及标题。
  • 可视化应该讲述一个故事,并且是为你的观众设计的。在创建可视化之前,请考虑您的目标受众—为非专业受众创建简单的可视化,为专业受众创建更详细的技术可视化。想一个故事,用你的形象化来讲述,这样你的形象化会给观众留下印象。

常见设计实践:

  • 颜色比符号更容易被感知。
  • 要在 2D 图上显示其他变量,请使用颜色、形状和大小。
  • 保持简单,不要给可视化带来太多信息。

活动 11:确定理想的可视化

以下可视化效果并不理想,因为它们不能很好地表示数据。为每个可视化回答以下问题:

  1. 这些可视化有哪些不好的方面?
  2. 我们如何改进可视化?为这两种场景绘制正确的可视化草图。

第一张图片是根据用户数量来展示前 30 名优酷:

Figure 2.43: Pie chart showing Top 30 YouTubers

图 2.43:显示前 30 名优酷的饼图

第二个可视化应该说明两天内在赌场玩某个游戏的人数:

图 2.44:显示两天赌场数据的折线图

注意:

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

总结

本章讨论了最重要的可视化。可视化分为比较、关系、组成、分布和地质图。对于每个绘图,都给出了描述、实例和设计实践。比较图,如折线图、条形图和雷达图,非常适合于比较多个变量或变量随时间的变化。关系图非常适合显示变量之间的关系。散点图、气泡图是散点图、相关图和热图的延伸。如果你把某件事看作整体的一部分,那么构图是理想的。我们首先介绍了饼图,然后继续介绍堆叠条形图、堆叠面积图和文氏图。对于深入了解数据分布情况的分布图,我们考虑了直方图、密度图、箱线图和小提琴图。关于地理空间数据,我们讨论了点地图、连接地图和点地图。最后,对什么是好的可视化给出了一些评论。在下一章中,我们将深入 Matplotlib 并创建我们自己的可视化。我们将涵盖本章中讨论的所有绘图。

三、深入 Matplotlib

学习目标

本章结束时,您将能够:

  • 描述 Matplotlib 的基本原理
  • 使用 Matplotlib 提供的内置绘图创建可视化效果
  • 自定义您的可视化图
  • 用 TeX 写数学表达式

在本章中,我们将学习如何使用 Matplotlib 自定义您的可视化。

简介

Matplotlib 可能是 Python 最流行的绘图库。它用于世界各地的数据科学和机器学习可视化。约翰·亨特从 2003 年开始开发 Matplotlib。它旨在模拟当时科学标准的 MATLAB 软件的命令。MATLAB 的全局风格等几个特性被引入到 Matplotlib 中,使 MATLAB 用户更容易过渡到 Matplotlib。

在我们开始使用 Matplotlib 创建我们的第一个可视化之前,我们将理解并尝试掌握这些绘图背后的概念。

Matplotlib 中的绘图概述

Matplotlib 中的绘图具有层次结构,嵌套 Python 对象以创建树状结构。每个图都封装在一个Figure对象中。这个Figure是可视化的顶层容器。它可以有多个轴,这些轴基本上是这个顶层容器中的独立图。

再深入一层,我们再次发现控制轴、刻度线、图例、标题、文本框、网格和许多其他对象的 Python 对象。所有这些对象都可以自定义。

绘图的两个主要组成部分如下:

  • Figure

    图形是最外层的容器,用作画布来绘制。它允许您在其中绘制多个图。它不仅保存 Axes 对象,还能够配置标题

  • Axes

    轴是实际的绘图或子绘图,这取决于您想要绘制单个还是多个可视化效果。它的子对象包括 x 轴和 y 轴、脊线和图例。

在更高的层次上观察这个设计,我们可以看到这个层次结构允许我们创建一个复杂的和可定制的可视化。

当我们看一个图形的“解剖”时,如下图所示,我们对一个有洞察力的可视化的复杂性有了一个概念。Matplotlib 通过调整网格xy 刻度、刻度标签图例,不仅让我们能够简单地显示数据,还可以围绕数据设计整个图形。这意味着我们可以修改绘图的每一个细节,从标题和图例开始,一直到脊柱上的大刻度和小刻度,以使其更具表现力:

Figure 3.1: Anatomy of a Matplotlib Figure

图 3.1:Matplotlib 图形的解剖

深入研究图形对象的解剖结构,我们可以观察到以下组件:

  • :连接轴刻度线的线
  • 标题:整个图表对象的文字标签
  • 传说:描述绘图内容
  • 网格:用作刻度线延伸的垂直线和水平线
  • X/Y 轴标签:脊柱下方 X/Y 轴的文字标签
  • 次要刻度:主要刻度之间的小数值指示器
  • 次要刻度标签:将在次要刻度处显示的文本标签
  • 主刻度:脊柱上的主数值指示器
  • 主要刻度标签:将在主要刻度处显示的文本标签
  • 线:用线连接数据点的绘图类型
  • 标记:用定义的标记绘制每个数据点的绘图类型

在本书中,我们将重点介绍 Matplotlib 的子模块 pyplot ,它提供了类似 MATLAB 的绘图。

Pyplot 基础知识

pyplot 包含一个更简单的创建可视化的界面,允许用户绘制数据,而无需明确配置图形本身。它们被隐式和自动地配置以实现期望的输出。使用别名plt引用导入的子模块很方便,如下所示:

import matplotlib.pyplot as plt

以下部分描述了使用 pyplot 时执行的一些常见操作。

创造图表

我们用plt.figure()创建一个新的。这个函数返回一个 Figure 实例,但是它也被传递到后端。接下来的每个与图形相关的命令都应用于当前图形,并且不需要知道图形实例。

默认情况下,该图的宽度为 6.4 英寸,高度为 4.8 英寸,dpi 为 100。要更改图的默认值,我们可以使用参数figsizedpi

下面的代码片段显示了我们如何操作一个图形:

plt.figure(figsize=(10, 5)) #To change the width and the height 
plt.figure(dpi=300) #To change the dpi

收盘数字

不再使用的图形应该通过显式调用plt.close()来关闭,这样也可以有效清理内存。

如果未指定任何内容,当前图形将被关闭。要关闭特定的图形,您可以提供对图形实例的引用或提供图形编号。要找到图形对象的编号,我们可以使用number属性,如下所示:

plt.gcf().number

使用plt.close('all'),所有图形将被关闭。以下示例显示了如何创建和关闭图形:

plt.figure(num=10) #Create Figure with Figure number 10
plt.close(10) #Close Figure with Figure number 10

格式化字符串

在我们实际绘制一些东西之前,让我们快速讨论一下格式字符串。它们是指定颜色标记类型线条样式的简洁方式。格式字符串被指定为“[color][marker][line]”,其中每个项目都是可选的。如果color是格式字符串的唯一参数,您可以使用任何matplotlib.colors。Matplotlib 可以识别以下格式:

  • RGB 或 RGBA 浮点元组(例如,(0.2,0.4,0.3)或(0.2,0.4,0.3,0.5))
  • RGB 或 RGBA 十六进制字符串(例如,' #0F0F0F '或' #0F0F0F ')

下图是如何以一种特定格式表示颜色的示例:

Figure 3.2 Color specified in string format

图 3.2 以字符串格式指定的颜色

下图显示了所有可用的标记选项:

Figure 3.3: Markers in format strings

图 3.3:格式字符串中的标记

下图显示了所有可用的线型:

Figure 3.4: Line styles

图 3.4:线条样式

标绘

使用plt.plot([x], y, [fmt]),可以将数据点绘制为线和/或标记。该函数返回表示打印数据的线 2D 对象列表。默认情况下,如果不提供格式字符串,数据点将使用直线和实线连接。plt.plot([0, 1, 2, 3], [2, 4, 6, 8])生成一个图,如下图所示。由于x是可选的,默认值为[0, …, N-1]plt.plot([2, 4, 6, 8])会产生相同的结果:

Figure 3.5: Plotting data points as a line

图 3.5:将数据点绘制成一条线

如果您想要绘制标记而不是线条,您可以指定任何标记类型的格式字符串。例如,plt.plot([0, 1, 2, 3], [2, 4, 6, 8], 'o')将数据点显示为圆形,如下图所示:

图 3.6:用标记(圆圈)绘制数据点

要绘制多个数据对,可以使用语法plt.plot([x], y, [fmt], [x], y2, [fmt2], …)plt.plot([2, 4, 6, 8], 'o', [1, 5, 9, 13], 's')结果如下图。同样,您可以多次使用plt.plot,因为我们使用的是同一个图形和轴:

Figure 3.7: Plotting data points with multiple markers

图 3.7:用多个标记绘制数据点

可以使用任何线 2D 属性代替格式字符串来进一步自定义绘图。例如,下面的代码片段显示了我们如何额外指定linewidthmarkersize:

plt.plot([2, 4, 6, 8], color='blue', marker='o', linestyle='dashed', linewidth=2, markersize=12)

使用熊猫数据帧绘图

使用pandas.DataFrame作为数据源非常简单。您可以不提供 x 和 y 值,而是在数据参数中提供pandas.DataFrame,并给出xy的键,如下所示:

plt.plot('x_key', 'y_key', data=df)

显示图形

plt.show()用于显示一个图形或多个图形。要在 Jupyter 笔记本中显示数字,只需在代码开头设置%matplotlib inline命令。

保存数字

plt.savefig(fname)保存当前图形。您可以指定一些有用的可选参数,如dpiformattransparent。下面的代码片段给出了一个如何保存图形的示例:

plt.figure()
plt.plot([1, 2, 4, 5], [1, 3, 4, 3], '-o')
plt.savefig('lineplot.png', dpi=300, bbox_inches='tight')
#bbox_inches='tight' removes the outer white margins

注意

所有练习和活动都将在 Jupyter 笔记本中开发。请从https://GitHub . com/trainingypbackt/Data-Visualization-with-Python下载带有所有准备好的模板的 GitHub 资源库。

练习 3:创建简单的可视化

在本练习中,我们将使用 Matplotlib 创建第一个简单的图:

  1. Open the Jupyter Notebook exercise03.ipynb from the Lesson03 folder to implement this exercise.

    导航到该文件的路径,并在命令行中键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    import numpy as np
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  3. 显式创建一个图形,并将 dpi 设置为 200:

    plt.figure(dpi=200)
    
  4. Plot the following data pairs (x, y) as circles, which are connected via line segments: (1, 1), (2, 3), (4, 4), (5, 3) . Then, visualize the plot:

    plt.plot([1, 2, 4, 5], [1, 3, 4, 3], '-o')
    plt.show()
    

    您的输出应该如下所示:

    Figure 3.8: A simple visualization that was created with the help given data pairs connected via line segments

    图 3.8:在给定数据对的帮助下创建的简单可视化,并通过线段连接
  5. 使用plt.savefig()方法保存绘图。这里,我们可以在方法中提供一个文件名,或者指定完整的路径:

    plt.savefig(exercise03.png);
    

基本文本和图例功能

我们在本主题中讨论的所有函数,除了图例之外,都创建并返回一个matplotlib.text.Text()实例。我们在这里提到它,以便您知道所有讨论的属性也可以用于其他功能。所有文本功能如图 3.9 所示。

标签

Matplotlib 提供了一些标签功能,我们可以用它们来设置 x 轴和 y 轴的标签。plt.xlabel()plt.ylabel()功能用于设置当前轴的标签。set_xlabel()set_ylabel()功能用于设置指定轴的标签。

示例:

ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')

标题

一个标题描述了一个特定的图表。标题位于中心、左边缘或右边缘的轴上方。标题有两个选项–您可以设置图形标题轴的标题suptitle()功能设置当前指定图形的标题。title()功能有助于设置当前轴和指定轴的标题。

示例:

fig = plt.figure()
fig.suptitle('Suptitle', fontsize=10, fontweight='bold')

这将创建一个粗体数字标题,文本标题和字体大小为 10。

文本

对于文本,有两个选项–您可以将文本添加到图形中,也可以将文本添加到轴中。figtext(x, y, text)text(x, y, text)功能在位置xy为图形添加文本。

示例:

ax.text(4, 6, 'Text in Data Coords', bbox={'facecolor': 'yellow', 'alpha':0.5, 'pad':10})

这将创建一个文本为“数据坐标中的文本”的黄色文本框。

标注

与放置在轴上任意位置的文本相比,标注用于标注绘图的某些特征。在标注中,有两个位置需要考虑:标注的位置xy和标注的位置,文本xytext。指定参数arrowprops非常有用,它会产生一个指向标注位置的箭头。

示例:

ax.annotate('Example of Annotate', xy=(4,2), xytext=(8,4), arrowprops=dict(facecolor='green', shrink=0.05))

这将创建一个指向数据坐标(4,2)的绿色箭头,文本“标注示例”位于数据坐标(8,4)处:

Figure 3.9: Implementation of Text Commands

图 3.9:文本命令的实现

传说

为了给你的轴添加一个传说,我们必须在艺术家创作时指定label参数。为当前轴调用plt.legend()或为特定轴调用Axes.legend()将添加图例。loc参数指定图例的位置。

示例:

…
plt.plot([1, 2, 3], label='Label 1')
plt.plot([2, 4, 3], label='Label 2')
plt.legend()
…

下图说明了该示例:

Figure 3.10: Legend example

图 3.10:图例示例

活动 12:使用线图可视化股票趋势

在本活动中,我们将创建一个折线图来显示股票趋势。让我们看看下面的场景:你对投资股票感兴趣。你下载了“五大巨头”:亚马逊、谷歌、苹果、脸书和微软的股价:

  1. 用熊猫读取位于子文件夹data的数据。
  2. 使用 Matplotlib 创建一个折线图,可视化所有五家公司过去五年的收盘价(整个数据序列)。添加标签、标题和图例,使可视化不言自明。使用plt.grid()给你的图添加一个网格。
  3. 执行上述步骤后,预期输出应该如下:

Figure 3.11: Visualization of stock trends of five companies

图 3.11:五家公司股票趋势的可视化

注意:

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

基本绘图

在这一节中,我们将探讨不同类型的基本绘图。

条形图

plt.bar(x, height, [width])创建竖条图。对于横条,使用plt.barh()功能。

重要参数:

  • x:指定条的 x 坐标
  • height:指定条的高度
  • width(可选):指定所有条的宽度;默认值为 0.8

示例:

plt.bar(['A', 'B', 'C', 'D'], [20, 25, 40, 10]) 

上面的代码创建了一个条形图,如下图所示:

Figure 3.12: A simple bar chart

图 3.12:一个简单的条形图

如果你想有子类别,你必须多次使用plt.bar()功能,移动 x 坐标。这在下面的示例中完成,并在下面的图表中说明。arange()函数是 NumPy 包中的一种方法,在给定的时间间隔内返回均匀间隔的值。gca()功能有助于获取任何当前图形上的当前轴的实例。set_xticklabels()功能用于设置带有给定字符串标签列表的 x 刻度标签。

示例:

…
labels = ['A', 'B', 'C', 'D']
x = np.arange(len(labels))
width = 0.4
plt.bar(x – width / 2, [20, 25, 40, 10], width=width)
plt.bar(x – width / 2, [30, 15, 30, 20], width=width)
# Ticks and tick labels must be set manually
plt.ticks(x)
ax = plt.gca()	
ax.set_xticklabels(labels)
…

上面的代码创建了一个带有子类别的条形图,如下图所示:

Figure 3.13: Bar chart with subcategories

图 3.13:带有子类别的条形图

活动 13:创建用于电影比较的条形图

在本活动中,我们将使用条形图来比较电影分数。给你五部烂番茄评分的电影。Tomatometer 是对电影给予正面评价的认可 Tomatometer 影评人的百分比。受众分数是 5 分中给出 3.5 分或更高分数的用户的百分比。在五部电影中比较这两个分数:

  1. 使用 pandas 读取位于子文件夹数据中的数据。
  2. 使用 Matplotlib 创建一个视觉上吸引人的条形图,比较所有五部电影的两个分数。
  3. 使用电影标题作为 x 轴的标签。对 y 轴刻度使用 20 的间隔百分比,对 5 的间隔使用次要刻度。给绘图加上一个图例和合适的标题。
  4. 执行上述步骤后,预期输出应该如下:

Figure 3.14: Bar plot comparing scores of five movies

图 3.14:对比五部电影评分的条形图

注意:

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

饼图

plt.pie(x, [explode], [labels], [autopct])函数创建一个饼图。

重要参数:

  • x:指定切片大小。
  • explode(可选):指定每个切片的半径偏移分数。分解数组的长度必须与 x 数组相同。
  • labels(可选):指定每个切片的标签。
  • autopct(可选):根据指定的格式字符串显示切片内的百分比。示例:“%1.1f%%”。

示例:

…
plt.pie([0.4, 0.3, 0.2, 0.1], explode=(0.1, 0, 0, 0), labels=['A', 'B', 'C', 'D'])
…

下图显示了上述代码的结果:

Figure 3.15: Basic pie chart

图 3.15:基本饼图

练习 4:创建用水量饼图

在本练习中,我们将使用饼图来可视化用水量:

  1. Open the Jupyter Notebook exercise04.ipynb from the Lesson03 folder to implement this exercise.

    导航到该文件的路径,并在命令行中键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import statements
    import pandas as pd
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  3. 用熊猫读取位于子文件夹数据中的数据:

    # Load dataset
    data = pd.read_csv('./data/water_usage.csv')
    
  4. 使用饼图来可视化用水量。使用分解参数突出显示您选择的一种用法。显示每个切片的百分比,并添加标题:

    # Create figure
    plt.figure(figsize=(8, 8), dpi=300)
    # Create pie plot
    plt.pie('Percentage', explode=(0, 0, 0.1, 0, 0, 0), labels='Usage', data=data, autopct='%.0f%%')
    # Add title
    plt.title('Water usage')
    # Show plot
    plt.show()
    

下图显示了前面代码的输出:

Figure 3.16: Pie chart for water usage

图 3.16:用水量饼图

堆叠条形图

一个堆叠条形图使用与条形图相同的plt.bar功能。对于每个堆叠条形图,必须调用plt.bar函数,并且必须从第二个堆叠条形图开始指定bottom参数。通过下面的例子,这一点将变得很清楚:

…
plt.bar(x, bars1)
plt.bar(x, bars2, bottom=bars1)
plt.bar(x, bars3, bottom=np.add(bars1, bars2))
…

下图显示了上述代码的结果:

Figure 3.17: Stacked bar chart

图 3.17:堆叠条形图

活动 14:创建堆叠条形图以可视化餐厅表现

在本活动中,我们将使用堆叠条形图来可视化餐厅的表现。让我们看看下面的场景:你是一家餐馆的老板,由于一项新的法律,你必须引入一个禁烟日。为了尽可能减少损失,您需要想象每天有多少销售额,按吸烟者和不吸烟者分类:

  1. 使用给定的数据集并创建一个矩阵,其中的元素包含每天的总账单和吸烟者/不吸烟者的账单。

  2. 创建一个堆积条形图,将吸烟者和非吸烟者每天的总账单堆积在一起。添加图例、标签和标题。

  3. After executing the preceding steps, the expected output should be as follows:

    Figure 3.18: Stacked bar chart showing performance of restaurant on different days

图 3.18:显示餐厅不同日子表现的堆叠条形图

注意:

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

堆叠面积图

plt.stackplot(x, y)创建堆叠面积图。

重要参数:

  • x:指定数据系列的 x 值。
  • y:指定数据系列的 y 值。对于多个系列,无论是 2d 阵列,还是任意数量的 1D 阵列,调用以下函数:plt.stackplot(x, y1, y2, y3, …).
  • labels(可选):将标签指定为每个数据系列的列表或元组。

示例:

…
plt.stackplot([1, 2, 3, 4], [2, 4, 5, 8], [1, 5, 4, 2])
…

下图显示了上述代码的结果:

Figure 3.19: Stacked area chart

图 3.19:堆叠面积图

活动 15:使用堆叠面积图比较智能手机销量

在本活动中,我们将使用堆叠面积图来比较智能手机的销售单位。让我们看看下面的场景:你想投资五大智能手机制造商之一。将季度销售单位视为整体的一部分可能是投资哪家公司的好指标:

  1. 使用 pandas 读取位于子文件夹数据中的数据。
  2. 创建一个视觉上吸引人的堆叠面积图。添加图例、标签和标题。
  3. 执行上述步骤后,预期输出应该如下:

Figure 3.20: Stacked area chart comparing sales units of different smartphone manufacturers

图 3.20:不同智能手机厂商销量对比的堆叠面积图

注意:

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

直方图

plt.hist(x)创建直方图。

重要参数:

  • x:指定输入值
  • bins:(可选):将面元数指定为整数,或将面元边指定为列表
  • range:(可选):以元组的形式指定容器的下限和上限
  • density:(可选):如果为真,直方图表示概率密度

示例:

…
plt.hist(x, bins=30, density=True)
…

下图显示了上述代码的结果:

Figure 3.21: Histogram

图 3.21:直方图

plt.hist2d(x, y)创建 2D 直方图。下图显示了 2D 历史图的一个示例:

Figure 3.22: 2D histogram with color bar

图 3.22:带颜色条的 2D 直方图

箱线图

plt.boxplot(x)创建方框图。

重要参数:

  • x:指定输入数据。它为单个框指定一个 1D 数组,或者为多个框指定一系列数组。
  • notch:可选:如果为真,将在图中添加凹口,以指示中位数周围的置信区间。
  • labels:可选:将标签指定为序列。
  • showfliers:可选:默认为真,超出上限绘制异常值。
  • showmeans:可选:如果为真,则显示算术平均值。

示例:

…
plt.boxplot([x1, x2], labels=['A', 'B'])
…

下图显示了上述代码的结果:

Figure 3.23: Box plot

图 3.23:方框图

活动 16:使用直方图和箱线图来可视化智商

在本练习中,我们将使用直方图和方框图来可视化智商。

注意

plt.axvline(x, [color=…], [linestyle=…])x位置画一条垂直线。

  1. 为给定的智商分数绘制一个包含 10 个面元的直方图。智商分数呈正态分布,平均值为 100,标准差为 15。将平均值可视化为垂直的红色实线,用垂直虚线表示标准偏差。添加标签和标题。

  2. 创建一个方框图来可视化相同的智商分数。添加标签和标题。

  3. 为不同测试组的每个智商分数创建一个方框图。添加标签和标题。

  4. The expected output for step 1 is as follows:

    Figure 3.24: Histogram for IQ test

    图 3.24:智商测试直方图
  5. The expected output for step 2 is as follows:

    Figure 3.25: Box plot for IQ scores

    图 3.25:智商分数的方框图
  6. The expected output for step 3 is as follows:

    Figure 3.26: Box plot for IQ scores of different test groups

图 3.26:不同测试组智商分数的箱线图

注意:

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

散点图

plt.scatter(x, y)创建 y 对 x 的散点图,可选地改变标记大小和/或颜色。

重要参数:

  • xy:指定数据位置。
  • s:可选:以点的平方指定标记大小。
  • c:可选:指定标记颜色。如果指定了数字序列,这些数字将被映射到颜色映射的颜色。

示例:

…
plt.scatter(x, y)
…

下图显示了上述代码的结果:

Figure 3.27: Scatter plot

图 3.27:散点图

活动 17:使用散点图来可视化各种动物之间的相关性

在本练习中,我们将使用散点图来显示数据集中的相关性。让我们看看下面的场景:给你一个包含各种动物信息的数据集。可视化各种动物属性之间的相关性:

注意

Axes.set_xscale('log')Axes.set_yscale('log')分别将 x 轴和 y 轴的刻度改为对数刻度。

  1. 给定的数据集不完整。过滤数据,这样你最终得到的样本就包含了一个体重和最长寿命。根据动物种类对数据进行分类。
  2. 创建散点图,可视化体重和最大寿命之间的相关性。根据数据样本的类别,使用不同的颜色对数据样本进行分组。添加图例、标签和标题。x 轴和 y 轴都使用对数刻度。
  3. 执行上述步骤后,预期输出应该如下:

Figure 3.28: Scatter plot on animal statistics

图 3.28:动物统计的散点图

注意:

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

气泡图

plt.scatter功能用于创建气泡图。要可视化第三个或第四个变量,可以使用参数s(比例)和c(颜色)。

示例:

…
plt.scatter(x, y, s=z*500, c=c, alpha=0.5)
plt.colorbar()
…

下图显示了上述代码的结果:

Figure 3.29: Bubble plot with color bar

图 3.29:带颜色条的气泡图

布局

在 Matplotlib 中定义可视化布局有多种方法。我们将从子图开始,以及如何使用紧凑布局来创建视觉上吸引人的绘图,然后覆盖 GridSpec ,这为创建多绘图提供了更灵活的方式。

子图

并排显示几个图通常很有用。Matplotlib 提供了子绘图的概念,子绘图是一个图形中的多个轴。这些图可以是网格图、嵌套图等。

探索以下选项来创建子绘图:

  • plt.subplots(nrows, ncols)创建一个图表和一组子图。
  • plt.subplot(nrows, ncols, index)或等效的plt.subplot(pos)给当前图形增加一个子图。指数从 1 开始。plt.subplot(2, 2, 1)相当于plt.subplot(221)
  • Figure.subplots(nrows, ncols)向指定的图形添加一组子绘图。
  • Figure.add_subplot(nrows, ncols, index)或等效的Figure.add_subplot(pos)给指定的图形添加一个子图。

要共享 x 轴或 y 轴,必须分别设置参数sharexsharey。该轴将具有相同的限制、刻度和比例。

plt.subplotFigure.add_subplot可以选择设置投影。对于极坐标投影,设置projection='polar'参数或设置parameter polar=True参数。

例 1:

…
fig, axes = plt.subplots(2, 2)
axes = axes.ravel()
for i, ax in enumerate(axes):
    ax.plot(series[i])
…
…
for i in range(4):
    plt.subplot(2, 2, i+1)
    plt.plot(series[i])
…

两个示例产生相同的结果,如下图所示:

Figure 3.30: Subplots

图 3.30:子图

例 2:

fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)
axes = axes.ravel()
for i, ax in enumerate(axes):
    ax.plot(series[i])

sharexsharey设置为真,结果如下图所示。这样可以进行更好的比较:

Figure 3.31: Subplots with a shared x and y axis

图 3.31:共享 x 轴和 y 轴的子绘图

紧凑布局

plt.tight_layout()调整子图参数,使子图在图中很好的契合。

示例:

如果不使用plt.tight_layout(),子图可能会重叠:

…
fig, axes = plt.subplots(2, 2)
axes = axes.ravel()
for i, ax in enumerate(axes):
    ax.plot(series[i])
    ax.set_title('Subplot ' + str(i))
…

下图显示了上述代码的结果:

Figure 3.32: Subplots with no layout option

图 3.32:没有布局选项的子图

使用plt.tight_layout()不会导致子绘图重叠:

…
fig, axes = plt.subplots(2, 2)
axes = axes.ravel()
for i, ax in enumerate(axes):
    ax.plot(series[i])
    ax.set_title('Subplot ' + str(i))
plt.tight_layout()
…

下图显示了上述代码的结果:

Figure 3.33: Subplots with a tight layout

图 3.33:布局紧凑的子图

雷达图

雷达图,也称为蜘蛛网络图,可视化多个变量,每个变量绘制在自己的轴上,形成一个多边形。所有轴都是径向排列的,从中心开始,彼此之间的距离相等,并且具有相同的刻度。

练习 5:绘制雷达图

在本练习中,将逐步展示如何创建雷达图:

  1. Open the Jupyter Notebook exercise05.ipynb from the Lesson03 folder to implement this exercise.

    导航到该文件的路径,并在命令行中键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import settings
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  3. 以下数据集包含四名员工的五种不同属性的评分:

    # Sample data
    # Attributes: Efficiency, Quality, Commitment, Responsible Conduct, Cooperation
    data = pd.DataFrame({
        'Employee': ['A', 'B', 'C', 'D'],
        'Efficiency': [5, 4, 4, 3,],
        'Quality': [5, 5, 3, 3],
        'Commitment': [5, 4, 4, 4],
        'Responsible Conduct': [4, 4, 4, 3],
        'Cooperation': [4, 3, 4, 5]
    })
    
  4. 创建角度值并关闭绘图:

    attributes = list(data.columns[1:])
    values = list(data.values[:, 1:])
    employees = list(data.values[:, 0])
    angles = [n / float(len(attributes)) * 2 * np.pi for n in range(len(attributes))]
    # Close the plot
    angles += angles[:1]
    values = np.asarray(values)
    values = np.concatenate([values, values[:, 0:1]], axis=1)
    
  5. 用极坐标投影创建子图。设置一个紧凑的布局,这样就不会有任何重叠:

    # Create figure
    plt.figure(figsize=(8, 8), dpi=150)
    # Create subplots
    for i in range(4):
        ax = plt.subplot(2, 2, i + 1, polar=True)
        ax.plot(angles, values[i])
        ax.set_yticks([1, 2, 3, 4, 5])
        ax.set_xticks(angles)
        ax.set_xticklabels(attributes)
        ax.set_title(employees[i], fontsize=14, color='r')
    # Set tight layout
    plt.tight_layout()
    # Show plot
    plt.show()
    

下图显示了前面代码的输出:

Figure 3.34: Radar charts

图 3.34:雷达图

格思特

matplotlib.gridspec.GridSpec(nrows, ncols)指定将放置子图的网格的几何形状。

示例:

…
gs = matplotlib.gridspec.GridSpec(3, 4)
ax1 = plt.subplot(gs[:3, :3])
ax2 = plt.subplot(gs[0, 3])
ax3 = plt.subplot(gs[1, 3])
ax4 = plt.subplot(gs[2, 3])
ax1.plot(series[0])
ax2.plot(series[1])
ax3.plot(series[2])
ax4.plot(series[3])
plt.tight_layout()
…

下图显示了上述代码的结果:

图 3.35: GridSpec

活动 18:创建带有边缘直方图的散点图

在本练习中,我们将使用 GridSpec 可视化带有边缘直方图散点图:

  1. 在之前的活动中已经使用过的给定数据集AnAge不完整。过滤数据,这样你最终得到的样本就包含了一个体重和最长寿命。选择所有体重小于 20,000 的 Aves 类样品。
  2. 创建具有受约束布局的图形。创建一个 4x4 大小的 gridspec。创建大小为 3x3 的散点图和大小为 1x3 和 3x1 的边缘直方图。添加标签和图形标题。
  3. 执行上述步骤后,预期输出应该如下:

图 3.36:带有边缘直方图的散点图

注意:

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

图像

如果您想在可视化中包含图像,或者您正在处理图像数据,Matplotlib 提供了几个处理图像的功能。在本节中,我们将向您展示如何使用 Matplotlib加载保存绘制图像。

注意

本主题中使用的图像来自https://unsplash.com/

基本图像操作

以下是用于设计图像的基本操作:

加载图像

如果您遇到 Matplotlib 不支持的图像格式,我们建议使用枕头库加载图像。在 Matplotlib 中,加载图像是图像子模块的一部分。我们使用别名mpimg作为子模块,如下所示:

import matplotlib.image as mpimg

mpimg.imread(fname)读取图像并将其作为numpy.array返回。对于灰度图像,返回的数组有一个形状(高度,宽度),对于 RGB 图像(高度,宽度,3),对于 RGBA 图像(高度,宽度,4)。数组值的范围从 0 到 255。

保存图像

mpimg.imsave(fname, array)numpy.array保存为图像文件。如果没有给出format参数,格式是从文件扩展名推导出来的。通过可选参数vminvmax,可以手动设置颜色限制。对于灰度图像,可选参数cmap的默认值为'viridis';你可能想把它改成'gray'

绘制单个图像

plt.imshow(img)显示图像并返回AxesImage。对于具有形状(高度、宽度)的灰度图像,图像阵列使用颜色图进行可视化。默认的颜色图是'viridis',如图 3.38 所示。为了实际可视化灰度图像,颜色图必须设置为'gray',即plt.imshow(img, cmap='gray'),如下图所示。灰度、RGB 和 RGBA 图像的值可以是floatuint8,范围分别为[0…1][0…255]。要手动定义数值范围,必须指定参数vminvmax。下图显示了 RGB 图像的可视化效果:

Figure 3.37: Grayscale image with default viridis colormap

图 3.37:默认维里迪斯色图的灰度图像

下图显示了带有灰色色图的灰度图像:

Figure 3.38: Grayscale image with gray colormap

图 3.38:带有灰色色图的灰度图像

下图显示了一幅 RGB 图像:

Figure 3.39: RGB image

图 3.39: RGB 图像

有时,深入了解颜色值可能会有所帮助。我们可以简单地在图像图中添加一个颜色条。建议使用高对比度的色彩图,例如,'jet':

…
plt.imshow(img, cmap='jet')
plt.colorbar()
…

下图说明了前面的示例:

Figure 3.40: Image with jet colormap and color bar

图 3.40:带有喷射色图和颜色条的图像

深入了解图像值的另一种方法是绘制直方图,如下图所示。要绘制图像阵列的直方图,必须使用numpy.ravel展平阵列:

…
plt.hist(img.ravel(), bins=256, range=(0, 1))
…

下图显示了前面代码的输出:

Figure 3.41: Histogram of image values

图 3.41:图像值直方图

在网格中绘制多个图像

为了在一个网格中绘制多个图像,我们可以简单地使用plt.subplots并根据轴绘制一个图像:

…
fig, axes = plt.subplots(1, 2)
for i in range(2):
    axes[i].imshow(imgs[i])
…

下图显示了上述代码的结果:

图 3.42:网格中的多个图像

在某些情况下,去掉记号并添加标签是很好的。axes.set_xticks([])axes.set_yticks([])分别去除 x 记号和 y 记号。axes.set_xlabel('label')增加了一个标签:

…
fig, axes = plt.subplots(1, 2)
labels = ['coast', 'beach']
for i in range(2):
    axes[i].imshow(imgs[i])
    axes[i].set_xticks([])
    axes[i].set_yticks([])
    axes[i].set_xlabel(labels[i])
…

下图显示了上述代码的结果:

Figure 3.43: Multiple images with labels

图 3.43:带有标签的多个图像

活动 19:在网格中绘制多个图像

在本练习中,我们将在网格中绘制图像:

  1. 从子文件夹数据加载所有四个图像。
  2. 在 2x2 网格中可视化图像。移除轴并给每个图像一个标签。
  3. 执行上述步骤后,预期输出应该如下:

图 3.44:在 2x2 网格中可视化图像

注意:

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

写数学表达式

如果需要在代码内编写数学表达式,Matplotlib 支持 TeX 。你可以把你的数学表达式放在一对美元符号中,在任何文本中使用它。没有必要安装 TeX,因为 Matplotlib 自带解析器。

下面的代码给出了一个例子:

…
plt.xlabel('$x$')
plt.ylabel('$\cos(x)$')
…

下图显示了前面代码的输出:

图 3.45:演示数学表达式的图

TeX 示例:

  • '$\alpha_i>\beta_i$'生产
  • '$\sum_{i=0}^\infty x_i$'生产
  • '$\sqrt[3]{8}$'生产
  • '$\frac{3 - \frac{x}{2}}{5}$'生产

总结

在本章中,我们详细介绍了 Matplotlib,这是 Python 最流行的可视化库之一。我们从 pyplot 及其操作的基础开始,然后深入了解有助于丰富文本可视化的众多可能性。本章通过实例介绍了 Matplotlib 提供的最流行的绘图功能,包括比较图、合成图和分布图。这一章以如何形象化图像和写数学表达式为结尾。

在下一章中,我们将了解 Seaborn 图书馆。Seaborn 建立在 Matplotlib 的基础上,提供了更高级别的抽象,使视觉上吸引人的可视化。我们还将讨论高级可视化类型。

四、使用 Seaborn 简化可视化

学习目标

本章结束时,您将能够:

  • 解释为什么 Seaborn 比 Matplotlib 好
  • 高效设计视觉上吸引人的绘图
  • 创造有洞察力的图表

在本章中,我们将看到 Seaborn 与 Matplolib 的不同之处,并使用图构建有效的图。

简介

Matplotlib 不同, Seaborn 不是一个独立的 Python 库。它建立在 Matplotlib 的基础上,并提供了一个更高层次的抽象,使视觉上吸引人的统计可视化。Seaborn 的一个简洁的特性是能够与熊猫库中的数据帧集成。

通过 Seaborn,我们试图使可视化成为数据探索和理解的中心部分。在内部,Seaborn 对包含完整数据集的数据帧和数组进行操作。这使它能够执行语义映射和统计聚合,这对显示信息可视化是必不可少的。Seaborn 也可以单独用来改变 Matplotlib 可视化的样式和外观。

Seaborn 最突出的特点如下:

  • 不同主题的开箱即用的美丽绘图
  • 内置调色板,可用于显示数据集中的模式
  • 面向数据集的接口
  • 仍然允许复杂可视化的高级抽象

海鸟的优势

Seaborn 建立在 Matplotlib 之上,也解决了使用 Matplotlib 的一些主要痛点。

使用 Matplotlib 处理数据帧会增加一些不方便的开销。例如:简单地浏览数据集可能会占用大量时间,因为您需要一些额外的数据争论,以便能够使用 Matplotlib 绘制数据框中的数据。

然而,Seaborn 是为在数据帧和完整数据集阵列上运行而构建的,这使得这个过程更加简单。它在内部执行必要的语义映射和统计聚合,以生成信息图。以下是使用 Seaborn 库进行绘图的示例:

import seaborn as sns
import pandas as pd
sns.set(style="ticks")
data = pd.read_csv("data/salary.csv")
sns.relplot(x="Salary", y="Age", hue="Education", style="Education",
col="Gender", data=data)

这将创建以下图:

Figure 4.1: Seaborn Relation plot

图 4.1:海底关系图

幕后,西博恩用 Matplotlib 绘制绘图。尽管许多任务只需使用 Seaborn 就可以完成,但进一步的定制可能需要使用 Matplotlib。我们只提供了数据集中变量的名称以及它们在图中扮演的角色。与 Matplotlib 不同,没有必要将变量转换为可视化的参数。

其他痛点是默认的 Matplotlib 参数和配置。Seaborn 中的默认参数提供了更好的可视化效果,无需额外定制。我们将在接下来的主题中详细讨论这些默认参数。

对于已经熟悉 Matplotlib 的用户来说,Seaborn 的扩展微不足道,因为核心概念大多相似。

控制图表审美

正如我们之前提到的,Matplotlib 是高度可定制的。但这也有一个影响,那就是很难知道要调整什么样的设置来实现视觉上吸引人的绘图。相比之下,Seaborn 提供了几个定制的主题和一个高级界面来控制 Matplotlib 图形的外观。

下面的代码片段在 Matplotlib 中创建了一个简单的线图:

%matplotlib inline
import matplotlib.pyplot as plt
plt.figure()
x1 = [10, 20, 5, 40, 8]
x2 = [30, 43, 9, 7, 20]
plt.plot(x1, label='Group A')
plt.plot(x2, label='Group B')
plt.legend()
plt.show()

这是 Matplotlib 默认参数下的图:

Figure 4.2: Matplotlib line plot

图 4.2: Matplotlib 线出图

要切换到 Seaborn 默认值,只需调用set()功能:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
plt.figure()
x1 = [10, 20, 5, 40, 8]
x2 = [30, 43, 9, 7, 20]
plt.plot(x1, label='Group A')
plt.plot(x2, label='Group B')
plt.legend()
plt.show()

绘图如下:

Figure 4.3: Seaborn line plot

图 4.3:海伯恩线图

Seaborn 将 Matplotlib 的参数分为两组。第一组包含绘图美学的参数,而第二组缩放图形的各种元素,以便可以在不同的上下文中轻松使用,例如用于演示、海报等的可视化。

海鸟体形风格

为了控制风格,Seaborn 提供了两种方法:set_style(style, [rc])axes_style(style, [rc])

seaborn.set_style(style, [rc])设定绘图的审美风格。

参数:

  • style:参数字典或以下预配置集合之一的名称:darkgridwhitegriddarkwhiteticks
  • rc(可选):参数映射以覆盖预设的 Seaborn 样式字典中的值

这里有一个例子:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid")
plt.figure()
x1 = [10, 20, 5, 40, 8]
x2 = [30, 43, 9, 7, 20]
plt.plot(x1, label='Group A')
plt.plot(x2, label='Group B')
plt.legend()
plt.show()

这将产生以下图:

Figure 4.4: Seaborn line plot with whitegrid style

图 4.4:白色网格样式的海伯恩线图

seaborn.axes_style(style, [rc])返回绘图审美风格的参数字典。该函数可在 with 语句中用于临时更改样式参数。

以下是参数:

  • style:参数字典或以下预配置集合之一的名称:darkgridwhitegriddarkwhiteticks
  • rc(可选):参数映射以覆盖预设的 Seaborn 样式字典中的值。

这里有一个例子:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
plt.figure()
x1 = [10, 20, 5, 40, 8]
x2 = [30, 43, 9, 7, 20]
with sns.axes_style('dark'):
    plt.plot(x1, label='Group A')
    plt.plot(x2, label='Group B')
plt.legend()
plt.show()

审美只是暂时的改变。结果如下图所示:

Figure 4.5: Seaborn Line plot with dark axes style

图 4.5:深轴风格的海伯恩线图

为了进一步定制,您可以将参数字典传递给rc参数。您只能替代属于样式定义一部分的参数。

去除斧刺

有时,可能需要移除顶部和右侧的轴刺。

seaborn.despine(fig=None, ax=None, top=True, right=True, left=False, bottom=False, offset=None, trim=False)从绘图中移除顶部和右侧的刺。

下面的代码有助于移除轴脊:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("white")
plt.figure()
x1 = [10, 20, 5, 40, 8]
x2 = [30, 43, 9, 7, 20]
plt.plot(x1, label='Group A')
plt.plot(x2, label='Group B')
sns.despine()
plt.legend()
plt.show()

这将产生以下图:

Figure 4.6: Despined Seaborn line plot

图 4.6:去毛刺的海底线图

上下文

一组单独的参数控制绘图元素的比例。这是一种使用相同代码创建适合在需要更大或更小图的环境中使用的图的简便方法。为了控制上下文,可以使用两个函数。

seaborn.set_context(context, [font_scale], [rc])设置绘图环境参数。这不会改变绘图的整体风格,但会影响标签、线条等的大小。基础上下文是notebook,其他上下文是papertalkposter,它们分别是按 0.8、1.3 和 1.6 缩放的notebook参数的版本。

以下是参数:

  • context:参数字典或以下预配置集合之一的名称:纸张、笔记本、谈话或海报
  • font_scale(可选):独立缩放字体元素大小的缩放因子
  • rc(可选):参数映射以覆盖预设的 Seaborn 上下文词典中的值

以下代码有助于设置上下文:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_context("poster")
plt.figure()
x1 = [10, 20, 5, 40, 8]
x2 = [30, 43, 9, 7, 20]
plt.plot(x1, label='Group A')
plt.plot(x2, label='Group B')
plt.legend()
plt.show()

上述代码生成以下输出:

Figure 4.7: Seaborn line plot with poster context

图 4.7:带有海报背景的 Seaborn 线条图

seaborn.plotting_context(context, [font_scale], [rc])返回一个参数字典来缩放图形的元素。此函数可与语句一起使用,以临时更改上下文参数。

以下是参数:

  • context:参数字典或以下预配置集合之一的名称:纸张、笔记本、谈话或海报
  • font_scale(可选):独立缩放字体元素大小的缩放因子
  • rc(可选):参数映射以覆盖预设的 Seaborn 上下文词典中的值

活动 20:使用箱线图比较不同测试组的智商得分

在本活动中,我们将使用 Seaborn 库的方框图来比较不同测试组的智商得分:

  1. 使用 pandas 读取位于子文件夹数据中的数据。
  2. 访问列中每个组的数据,将它们转换为列表,并将该列表分配给每个相应组的变量。
  3. 通过使用每个相应组的数据,使用前面的数据创建熊猫数据框。
  4. 使用 Seaborn 的boxplot功能为不同测试组的每个智商分数创建一个方框图。
  5. 使用whitegrid样式,将上下文设置为说话,去掉除底部的轴刺以外的所有轴刺。给绘图加上一个标题。
  6. 执行上述步骤后,最终输出应如下所示:

Figure 4.8: IQ scores of groups

图 4.8:各组的智商得分

注意:

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

色盘

颜色是你视觉化的一个非常重要的因素。如果使用有效,颜色可以显示数据中的模式,如果使用不当,颜色可以隐藏模式。Seaborn 使选择和使用适合您任务的调色板变得容易。color_palette()功能为许多可能的颜色生成方式提供了一个界面。

seaborn.color_palette([palette], [n_colors], [desat])返回颜色列表,从而定义调色板。

以下是参数:

  • palette(可选):调色板名称或无返回当前调色板。
  • n_colors(可选):调色板中的颜色数量。如果指定的颜色数量大于调色板中的颜色数量,颜色将被循环使用。
  • desat(可选):将每种颜色去饱和的比例。

您可以使用set_palette()设置所有绘图的调色板。该函数接受与color_palette()相同的参数。在接下来的章节中,我们将解释调色板如何被分成不同的组。

分类调色板

分类调色板最适合区分没有固有顺序的离散数据。Seaborn 中有六个默认主题:deepmutedbrightpasteldarkcolorblind。以下代码提供了每个主题的代码和输出:

import seaborn as sns
palette1 = sns.color_palette("deep")
sns.palplot(palette1)

下图显示了前面代码的输出:

图 4.9:深调色板
palette2 = sns.color_palette("muted")
sns.palplot(palette2)

下图显示了前面代码的输出:

图 4.10:静音调色板

下图显示了一个明亮的调色板:

palette3 = sns.color_palette("bright")
sns.palplot(palette3)

图 4.11:明亮的调色板

下图显示了柔和的调色板:

palette4 = sns.color_palette("pastel")
sns.palplot(palette4)

图 4.12:彩色调色板

下图显示了深色调色板:

palette5 = sns.color_palette("dark")
sns.palplot(palette5)

图 4.13:深色调色板
palette6 = sns.color_palette("colorblind")
sns.palplot(palette6)

下图显示了前面代码的输出:

Figure 4.14: Colorblind color palette

图 4.14:色盲调色板

顺序调色板

当数据范围从相对较低或不感兴趣的值到相对较高或感兴趣的值时,顺序调色板是合适的。下面的代码片段,以及它们各自的输出,让我们对顺序调色板有了更好的了解:

custom_palette2 = sns.light_palette("brown")
sns.palplot(custom_palette2)

下图显示了前面代码的输出:

Figure 4.15: Custom brown color palette

图 4.15:定制棕色调色板

在下面的代码中,通过将reverse参数设置为True,也可以反转前面的调色板:

custom_palette3 = sns.light_palette("brown", reverse=True)
sns.palplot(custom_palette3)

下图显示了前面代码的输出:

Figure 4.16: Custom reversed brown color palette

图 4.16:定制反转棕色调色板

发散调色板

Diverging color palettes用于由明确定义的中点组成的数据。重点放在高值和低值上。例如:如果您从某个基线人口中绘制某个特定区域的人口变化,最好使用不同的颜色图来显示人口的相对增加和减少。下面的代码片段和输出提供了对发散图的更好理解,其中我们使用了coolwarm模板,该模板内置于 Matplotlib 中:

custom_palette4 = sns.color_palette("coolwarm", 7)
sns.palplot(custom_palette4)

下图显示了前面代码的输出:

Figure 4.17: Coolwarm color palette

图 4.17:冷暖色调色板

您可以使用diverging_palette()功能创建自定义发散调色板。我们可以传递两个色调作为参数,以及调色板的总数。下面的代码片段和输出提供了更好的洞察力:

custom_palette5 = sns.diverging_palette(440, 40, n=7)
sns.palplot(custom_palette5)

下图显示了前面代码的输出:

Figure 4.18: Custom diverging color palette

图 4.18:自定义发散调色板

活动 21:使用热图寻找航班乘客数据中的模式

在本活动中,我们将使用热图来查找航班乘客数据中的模式:

  1. 使用 pandas 读取位于子文件夹数据中的数据。给定的数据集包含 2001 年至 2012 年航班乘客的月度数据。
  2. 使用热图可视化给定的数据。
  3. 使用自己的彩色地图。确保最低值是最暗的颜色,最高值是最亮的颜色。
  4. 执行上述步骤后,预期输出应该如下:

Figure 4.19: Heatmap of Flight Passengers data

图 4.19:航班乘客数据热图

注意:

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

海伯恩有趣的绘图

在上一章中,我们讨论了 Matplotlib 中的各种绘图,但仍有一些可视化的内容需要讨论。

条形图

在最后一章中,我们已经解释了如何用 Matplotlib 创建条形图。创建带有子组的条形图非常繁琐,但是 Seaborn 提供了一种非常方便的方法来创建各种条形图。它们也可以在 Seaborn 中使用,表示每个矩形高度的中心趋势估计,并使用误差线表示该估计的不确定性。

下面的示例让您很好地了解了这是如何工作的:

import pandas as pd
import seaborn as sns
data = pd.read_csv("data/salary.csv")
sns.set(style="whitegrid")
sns.barplot(x="Education", y="Salary", hue="District", data=data)

结果如下图所示:

Figure 4.20: Seaborn bar plot

图 4.20:海底酒吧图

活动 22:重温电影对比

在本活动中,我们将使用条形图来比较电影分数。你将得到五部烂番茄的电影。Tomatometer 是对电影给予正面评价的认可 Tomatometer 影评人的百分比。受众分数是 5 分中给出 3.5 分或更高分数的用户的百分比。在五部电影中比较这两个分数:

  1. 使用 pandas 读取位于子文件夹数据中的数据。
  2. 将数据转换为适用于 Seaborn 条形图功能的可用格式。
  3. 使用 Seaborn 创建一个视觉上吸引人的条形图,比较所有五部电影的两个分数。
  4. 执行上述步骤后,预期的输出应该如下所示:

Figure 4.21: Movie Scores comparison

图 4.21:电影评分对比

注意:

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

核密度估计

可视化数据集变量的分布通常很有用。Seaborn 提供了检查单变量和双变量分布的便捷函数。一种可能的方法是使用distplot()函数来观察 Seaborn 中的单变量分布。这将绘制直方图并拟合核密度估计值 ( KDE ,如下例所示:

%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
x = np.random.normal(size=50)
sns.distplot(x)

结果如下图所示:

Figure 4.22: KDE with histogram for univariate distribution

图 4.22:单变量分布直方图的 KDE

为了直观显示 KDE,Seaborn 提供了kdeplot()功能:

sns.kdeplot(x, shade=True)

下图显示了 KDE 曲线,以及曲线下的阴影区域:

Figure 4.23: KDE for univariate distribution

图 4.23:单变量分布的 KDE

绘制二元分布

为了可视化二元分布,我们将介绍三个不同的图。前两个图使用jointplot()函数,该函数创建了一个多面板图,显示了两个变量之间的联合关系和相应的边际分布。

散点图将每个观察点显示为xy轴上的点。此外,还显示了每个变量的直方图:

import pandas as pd
import seaborn as sns
data = pd.read_csv("data/salary.csv")
sns.set(style="white")
sns.jointplot(x="Salary", y="Age", data=data)

下图显示了带有边缘直方图的散点图:

Figure 4.28: Scatter plot with marginal histograms

图 4.24:带有边缘直方图的散点图

也可以使用 KDE 程序来可视化二元分布。联合分布显示为等高线图,如以下代码所示:

sns.jointplot('Salary', 'Age', data=subdata, kind='kde', xlim=(0, 500000), ylim=(0, 100))

结果如下图所示:

Figure 4.25: Contour plot

图 4.25:等高线图

可视化成对关系

为了可视化数据集中的多个成对二元分布,Seaborn 提供了pairplot()函数。该函数创建一个矩阵,其中非对角线元素显示每对变量之间的关系,对角线元素显示边际分布。

下面的例子让我们对此有了更好的理解:

%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
mydata = pd.read_csv("data/basic_details.csv")
sns.set(style="ticks", color_codes=True)
g = sns.pairplot(mydata, hue="Groups")

配对图,也称为相关图,如下图所示。所有变量对的散点图显示在非对角线上,而 kde 显示在对角线上。组以不同的颜色突出显示:

Figure 4.26: Seaborn pair plot

图 4.26:海伯恩配对图

小提琴绘图

可视化统计测量的另一种方法是使用小提琴绘图。他们将箱线图与我们之前描述的核密度估计过程相结合。它对变量的分布提供了更丰富的描述。此外,方框图中的四分位数和触须值显示在小提琴内部。

以下示例演示了小提琴绘图的用法:

import pandas as pd
import seaborn as sns
data = pd.read_csv("data/salary.csv")
sns.set(style="whitegrid")  
sns.violinplot('Education', 'Salary', hue='Gender', data=data, split=True, cut=0)

结果如下:

Figure 4.31: Seaborn violin plot

图 4.27:Seaborn 小提琴图

活动 23:使用小提琴图比较不同测试组的智商得分

在本活动中,我们将使用 Seaborn 图书馆提供的小提琴图来比较不同测试组的智商得分:

  1. 使用 pandas 读取位于子文件夹数据中的数据。
  2. 访问列中每个组的数据,将它们转换为列表,并将该列表分配给每个相应组的变量。
  3. 通过使用每个相应组的数据,使用前面的数据创建熊猫数据帧。
  4. 使用 Seaborn 的violinplot功能为不同测试组的每个智商分数创建一个方框图。
  5. 使用whitegrid样式,将上下文设置为说话,去掉除底部的轴刺以外的所有轴刺。给绘图加上一个标题。
  6. 执行上述步骤后,最终输出应该如下所示:

Figure 4.28: Violin plot showing IQ scores of different groups

图 4.28:显示不同组智商得分的小提琴图

注意:

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

海底多绘图

在前一个主题中,我们介绍了一个多绘图,即配对绘图。在这个话题中,我们想谈谈一种不同的方式来创造灵活的多绘图。

FacetGrid

面网格对于分别可视化多个变量的特定图非常有用。一个面网格最多可以绘制三个维度:rowcolhue。前两个与数组的行和列有明显的对应关系。hue是第三维度,用不同的颜色显示。FacetGrid类必须用数据框和构成网格的行、列或色调维度的变量名称初始化。这些变量应该是分类离散

seaborn.FacetGrid(data, row, col, hue, …)初始化用于绘制条件关系的多绘图网格。

以下是一些有趣的参数:

  • data:整齐的(“长格式”)数据帧,其中每一列对应一个变量,每一行对应一个观察值
  • row, col, hue:定义给定数据子集的变量,这些数据将绘制在网格中的独立面上
  • sharex, sharey(可选):跨行/列共享x / y
  • height(可选):每个面的高度(英寸)

初始化网格还没有在上面画出任何东西。为了可视化这个网格上的数据,必须使用FacetGrid.map()方法。您可以提供任何绘图功能以及要绘图的数据框中变量的名称。

FacetGrid.map(func, *args, **kwargs)对网格的每个面应用绘图功能。

以下是参数:

  • func:取数据和关键字参数的标绘函数。
  • *args:数据中标识要绘制的变量的列名。每个变量的数据按照变量指定的顺序传递给func
  • **kwargs:传递给绘图函数的关键字参数。

以下示例使用散点图可视化 FacetGrid:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
data = pd.read_csv("data/salary.csv")
g = sns.FacetGrid(subdata, col='District')
g.map(plt.scatter, 'Salary', 'Age')

Figure 4.29: FacetGrid with scatter plots

图 4.29:带有散点图的面网格

活动 24:排名前 30 的 YouTube 频道

在本活动中,我们将使用由 Seaborn 库提供的FacetGrid()功能来可视化前 30 个 YouTube 频道的订户总数和总浏览量。使用包含两列的 FacetGrid 可视化给定数据。第一列应该显示每个 YouTube 频道的订户数量,而第二列应该显示浏览量。以下是实施此活动的步骤:

  1. 使用 pandas 读取位于子文件夹数据中的数据。
  2. 访问列中每个组的数据,将它们转换为列表,并将该列表分配给每个相应组的变量。
  3. 通过使用每个相应组的数据,使用前面的数据创建熊猫数据框。
  4. 创建一个包含两列的 FacetGrid 来可视化数据。
  5. 执行上述步骤后,最终输出应该如下所示:

Figure 4.30: Subscribers and Views of top 30 YouTube channels

图 4.30:前 30 个 YouTube 频道的订户和浏览量

注意:

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

回归图

许多数据集包含多个定量变量,目标是找到这些变量之间的关系。我们之前提到了几个显示两个变量联合分布的函数。估计两个变量之间的关系是有帮助的。我们在这个主题中只讨论线性回归;但是,如果需要,Seaborn 提供了更广泛的回归功能。

为了可视化通过线性回归确定的线性关系,regplot()函数由 Seaborn 提供。下面的代码片段给出了一个简单的例子:

import numpy as np
import seaborn as sns
x = np.arange(100)
y = x + np.random.normal(0, 5, size=100)
sns.regplot(x, y)

regplot()函数绘制散点图、回归线和该回归的 95%置信区间,如下图所示:

Figure 4.31: Seaborn regression plot

图 4.31:Seaborn 回归图

活动 25:线性回归

在本练习中,我们将使用回归图来可视化线性关系:

  1. 使用 pandas 读取位于子文件夹数据中的数据。
  2. 过滤数据,这样你最终得到的样本就包含了一个体重和最长寿命。仅考虑哺乳动物类和体重低于 200,000 的样本。
  3. 创建一个回归图来可视化变量之间的线性关系。
  4. 执行上述步骤后,输出应该如下所示:

Figure 4.32: Linear regression for animal attribute relations

图 4.32:动物属性关系的线性回归

注意:

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

方形

在这一点上,我们将简单谈谈树图。树形图将分层数据显示为一组嵌套的矩形。每个组由一个矩形表示,矩形的面积与其值成正比。使用配色方案,可以表示层次结构:组、子组等等。与饼图相比,树形图可以有效利用空间。Matplotlib 和 Seaborn 不提供树地图,因此使用了建立在 Matplotlib 之上的 Squarify 库。Seaborn 是创建调色板的一个很好的补充。

下面的代码片段是一个基本的树图示例。它需要 Squarify 库:

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import squarify
colors = sns.light_palette("brown", 4)
squarify.plot(sizes=[50, 25, 10, 15], label=["Group A", "Group B", "Group C", "Group D"], color=colors)
plt.axis("off")
plt.show()

结果如下图所示:

Figure 4.44: Tree map

图 4.33:树形图

活动 26:重新审视水的使用

在本练习中,我们将使用树形图来可视化用于不同目的的水的百分比:

  1. 使用 pandas 读取位于子文件夹数据中的数据。
  2. 使用树状图来可视化用水量。
  3. 显示每个图块的百分比,并添加标题。
  4. 执行上述步骤后,预期输出应该如下:

Figure 4.34: Tree map of water usage

图 4.34:用水量树形图

注意:

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

总结

在这一章中,我们展示了 Seaborn 如何帮助创造视觉上吸引人的形象。我们讨论了控制图形美学的各种选项,例如图形样式、控制脊椎和设置可视化的上下文。我们详细讨论了调色板。为了可视化单变量和双变量分布,引入了进一步的可视化。此外,我们还讨论了可用于创建多图的 FacetGrids,以及作为分析两个变量之间关系的方法的回归图。最后,我们讨论了用于创建树图的 Squarify 库。在下一章中,我们将向您展示如何使用 Geoplotlib 库以各种方式可视化地理空间数据。

五、绘制地理空间数据

学习目标

本章结束时,您将能够:

  • 利用地理地图创建令人惊叹的地理可视化
  • 识别不同类型的地理空间图表
  • 演示包含用于绘图的地理空间数据的数据集
  • 解释绘制地理空间信息的重要性

在本章中,我们将使用 geoplotib 库来可视化不同的地理空间数据。

简介

geoblotlib是一个用于地理空间数据可视化的开源 Python 库。它有广泛的地理可视化,并支持硬件加速。它还为具有数百万个数据点的大型数据集提供了性能渲染。如前几章所述,Matplotlib 提供了可视化地理数据的方法。但是,Matplotlib 并不是为这个任务而设计的,因为它的接口复杂,使用起来不方便。Matplotlib 还限制了地理数据的显示方式。底图制图库支持该功能,因此您可以在世界地图上绘图。但是,这些包不支持在地图图块上绘制。

另一方面,Geoplotlib 正是为此目的而设计的,因此它不仅提供了地图图块,还允许交互性和简单的动画。它提供了一个简单的界面,允许访问强大的地理空间可视化。

注意

为了更好地了解 geo lotlib 的可用功能,您可以访问以下链接:https://github . com/Andrea-cuttone/geo lotlib/wiki/User-Guide

为了理解 Geoplotlib 的概念、设计和实现,让我们简单了解一下它的概念架构。馈入地理乐库的两个输入是您的数据源地图图块。正如我们将在后面看到的,地图切片可以被不同的提供者替换。这些输出不仅描述了在 Jupyter 笔记本中渲染图像的可能性,还描述了在交互式窗口中工作的可能性,该窗口允许缩放平移地图。地理乐库组件的模式如下:

Figure 5.1: Conceptual architecture of geoplotlib

图 5.1:地质图书馆的概念架构

geo lotlib使用了可以上下叠加的图层概念,为甚至复杂的可视化提供了强大的界面。它附带了几个易于设置和使用的常见可视化层。

从上图中我们可以看到geo lotlib建立在 NumPy / SciPyPyglet / OpenGL 之上。这些库负责数值运算和渲染。这两个组件都基于 Python,因此可以使用完整的 Python 生态系统。

地质图书馆的设计原则

仔细看看 Geoplotlib 的内部设计,我们可以看到它是围绕三个设计原则构建的:

  • Simplicity: Looking at the example provided here, we can quickly see that Geoplotlib abstracts away the complexity of plotting map tiles and the already provided layers such as dot-density and histogram. It has a simple API that provides common visualizations. These visualizations can be created using custom data with only a few lines of code. If our dataset comes with lat and lon columns, we can display those datapoints as dots on a map with five lines of code, like this:

    import geoplotlib
    from geoplotlib.utils import read_csv dataset = read_csv('./data/poaching_points_cleaned.csv')
    geoplotlib.dot(dataset)
    geoplotlib.show()
    

    除此之外,每个以前使用过 Matplotlib 的人理解 Geoplotlib 的语法都不会有问题,因为它的语法是受 Matplotlib 的语法启发的。

  • Integration: Geoplotlib visualizations are purely Python-based. This means that the generic Python code can be executed and other libraries such as pandas can be used for data wrangling purposes. We can manipulate and enrich our datasets using pandas DataFrames and later simply convert them into a Geoplotlib DataAccessObject, which we need for optimum compatibility, like this:

    import pandas as pd
    from geoplotlib.utils import DataAccessObject
    pd_dataset = pd.read_csv('./data/poaching_points_cleaned.csv')
    # data wrangling with pandas DataFrames here
    dataset = DataAccessObject(pd_dataset)
    

    Geoplotlib 完全集成到 Python 生态系统中。这使我们甚至可以在 Jupyter 笔记本中在线绘制地理数据。这种可能性允许我们快速迭代地设计可视化。

  • 性能:正如我们之前提到的,由于使用 NumPy 进行加速数值运算和 OpenGL 进行加速图形渲染,Geoplotlib 能够处理大量数据。

地理空间可视化

choropoleth 图Voronoi 镶嵌Delaunay 三角测量是本章将使用的几个地理空间可视化。这里提供了对它们的解释:

氯普勒斯图

这种地理图以阴影或彩色的方式显示一个国家的州等区域。阴影或颜色由一个或一组数据点决定。它给出了一个地理区域的抽象视图,以可视化不同区域之间的关系和差异。在图 5.21 中,我们可以看到美国每个州的阴影是由肥胖的百分比决定的。阴影越深,百分比越高。

沃罗诺伊镶嵌

Voronoi 镶嵌中,每对数据点基本上由一条与两个数据点具有相同距离的线分开。这种分离会创建单元格,对于每个给定点,这些单元格会标记哪个数据点更接近。数据点越接近,单元格越小。

Delaunay 三角剖分

一个德劳奈三角测量与沃罗诺伊镶嵌相关。当将每个数据点连接到共享一条边的其他数据点时,我们最终得到一个三角化的图。数据点越接近,三角形就越小。这给了我们一个关于特定区域点密度的视觉线索。当与颜色梯度相结合时,我们获得了关于兴趣点的见解,这可以与热图进行比较。

练习 6:可视化简单地理空间数据

在本练习中,我们将了解 Geoplotlib 绘图方法在点密度直方图Voronoi 图中的基本用法。为此,我们将利用世界各地发生的各种偷猎事件的数据:

  1. Open the Jupyter Notebook exercise06.ipynb from the Lesson05 folder to implement this exercise.

    为此,您需要导航到该文件的路径。在命令行终端中,键入:jupyter-lab

  2. 现在,您应该已经熟悉了使用 Jupyter 笔记本的过程。

  3. 打开exercise06.ipynb文件。

  4. 一如既往,首先,我们希望导入我们需要的依赖项。在这种情况下,我们将在没有熊猫的情况下工作,因为geoplotlib有自己的read_csv方法,使我们能够将. csv 文件读入DataAccessObject :

    # importing the necessary dependencies
    import geoplotlib
    from geoplotlib.utils import read_csv
    
  5. The data is being loaded in the same way as with the pandas read_csv method:

    dataset = read_csv('./data/poaching_points_cleaned.csv')
    

    注意

    前面的数据集可以在这里找到:https://bit.ly/2Xosg2b

  6. The dataset is stored in a DataAccessObject class that's provided by Geoplotlib. It does not have the same capabilities as pandas DataFrames. It's meant for the simple and quick loading of data so that you can create a visualization. If we print out this object, we can see the difference better. It gives us a basic overview of what columns are present and how many rows the dataset has:

    # looking at the dataset structure
    Dataset
    

    下图显示了前面代码的输出:

    Figure 5.2: Dataset structure

    图 5.2:数据集结构

    正如我们在前面的截图中看到的,数据集由 268 行和 6 列组成。每行由id_report唯一标识。date_report栏注明偷猎事件发生的日期。另一方面,created_date一栏注明了报告的创建日期。描述列提供了有关该事件的基本信息。latlon栏陈述了偷猎发生地的地理位置。

  7. Geoplotlib is compatible with pandas DataFrames as well. If you need to do some pre-processing with the data, it might make sense to use pandas right away:

    # csv import with pandas
    import pandas as pd
    pd_dataset = pd.read_csv('./data/poaching_points_cleaned.csv')
    pd_dataset.head()
    

    下图显示了前面代码的输出:

    Figure 5.3: First five entries of the dataset

    图 5.3:数据集的前五个条目

    注意

    Geoplotlib 要求您的数据集具有latlon列。这些列是纬度和经度的地理数据,用于确定如何绘图。

  8. To start with, we'll be using a simple DotDensityLayer that will plot each row of our dataset as a single point on a map. Geoplotlib comes with the dot method, which creates this visualization without further configurations:

    注意

    在这里设置好 DotDensityLayer 之后,我们需要调用show方法,这个方法会用给定的图层渲染地图。

    # plotting our dataset with points
    geoplotlib.dot(dataset)
    geoplotlib.show()
    

    下图显示了前面代码的输出:

    Figure 5.4: Dot-density visualization of poaching points

    图 5.4:偷猎点的点密度可视化

    只看数据集中的latlon值,不会给我们很好的思路。如果不在地图上可视化我们的数据点,我们就无法得出结论并深入了解数据集。在看渲染图的时候,我们可以看到有一些比较受欢迎的点比较多,也有一些不太受欢迎的点比较少。

  9. We now want to look at the point density some more. To better visualize the density, we have a few options. One of them is to use a histogram plot. We can define a binsize, which will allow us to set the size of the hist bins in our visualization. Geoplotlib provides the hist method, which will create a Histogram Layer on top of our map tiles:

    # plotting our dataset as a histogram
    geoplotlib.hist(dataset, binsize=20)
    geoplotlib.show()
    

    下图显示了前面代码的输出:

    Figure 5.5: Histogram visualization of poaching points

    图 5.5:偷猎点的直方图可视化

    直方图让我们更好地理解数据集的密度分布。看最后的绘图,可以看出有一些偷猎的热点。这也突出了没有任何偷猎事件的地区。

  10. Voronoi plots are also good for visualizing the density of data points. Voronoi introduces a little bit more complexity with several parameters such as cmap, max_area, and alpha. Here, cmap denotes the color of the map, alpha denotes the color of the alpha, and max_area denotes a constant that determines the color of the Voronoi areas. They are useful if you want to better fit your visualization into the data:

```py
# plotting a voronoi map
geoplotlib.voronoi(dataset, cmap='Blues_r', max_area=1e5, alpha=255)
geoplotlib.show()
```

下图显示了前面代码的输出:

Figure 5.6: Voronoi visualization of poaching points

图 5.6:挖角点的 Voronoi 可视化

如果我们将沃罗诺伊可视化与直方图进行比较,我们可以看到一个吸引了很多注意力的区域。图的中右边缘显示了相当大的深蓝色区域,中心更暗,这在直方图中很容易被忽略。

恭喜你!我们刚刚介绍了地理图书馆的基础知识。它有更多的方法,但是它们都有一个相似的 API,使得使用其他方法变得简单。既然我们看了一些非常基本的可视化,现在就看你来解决第一个活动了。

活动 27:在地图上绘制地理空间数据

在本活动中,我们将利用之前学习的使用 Geoplotlib 绘制数据的技能,并将其应用于在欧洲人口超过 10 万的城市中寻找密集区域的任务:

  1. Lesson05文件夹中打开 Jupyter 笔记本activity27.ipynb执行本活动。
  2. 在开始处理数据之前,我们需要导入依赖项。
  3. 使用pandas加载数据集。
  4. 加载数据集后,我们希望列出其中存在的所有数据类型。
  5. 一旦我们可以看到我们的数据类型是正确的,我们将把我们的LatitudeLongitude列映射到latlon列。
  6. 现在,将数据点绘制成点状图。
  7. 为了离解决我们给定的任务更近一步,我们需要获得每个国家的城市数量(前 20 个条目),并过滤掉人口大于零的国家。
  8. 将剩余数据绘制成点状图。
  9. 再次,过滤人口超过 100,000 的城市的剩余数据。
  10. 为了更好地理解地图上数据点的密度,我们想要使用 Voronoi 镶嵌图层
  11. 将数据进一步过滤到德国和英国等国家的城市。
  12. 最后,使用 Delaunay 三角测量图层找到人口最密集的区域。
  13. 观察点图的预期输出:

Figure 5.7: Dot-density visualization of the reduced dataset

图 5.7:简化数据集的点密度可视化

下面是沃罗诺伊图的预期输出:

Figure 5.8: Voronoi visualization of densely populated cities

图 5.8:人口稠密城市的沃罗诺伊可视化

以下是 Delaunay 三角测量的预期输出:

Figure 5.9: Delaunay triangle visualization of cities in Germany and Great Britain

图 5.9:德国和英国城市的德劳奈三角可视化

注意:

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

练习 7:用 GeoJSON 数据绘制坐标图

在本练习中,我们不仅想处理 GeoJSON 数据,还想了解如何创建cholopleth 可视化。它们对于在阴影区域显示统计变量特别有用。在我们的例子中,这些区域将是美国各州的轮廓。让我们用给定的 GeoJSON 数据创建一个 choropleth 可视化:

  1. Lesson05文件夹打开 Jupyter 笔记本exercise07.ipynb执行本练习。

  2. 加载本练习的依赖项:

    # importing the necessary dependencies
    import json
    import geoplotlib
    from geoplotlib.colors import ColorMap
    from geoplotlib.utils import BoundingBox
    
  3. 在我们创建实际的可视化之前,我们需要了解数据集的轮廓。由于 Geoplotlib 的geojson方法只需要一个到数据集的路径,而不是一个 DataFrame 或对象,所以我们不需要加载它。但是,由于我们仍然想看看我们处理的是什么类型的数据,所以我们必须打开 GeoJSON 文件,并将其作为json对象加载。有鉴于此,我们可以通过简单索引 :

    # displaying one of the entries for the states
    with open('data/National_Obesity_By_State.geojson') as data:
        dataset = json.load(data)
        first_state = dataset.get('features')[0]
    
        # only showing one coordinate instead of all points
        first_state['geometry']['coordinates'] = first_state['geometry']['coordinates'][0][0]
        print(json.dumps(first_state, indent=4))
    

    来访问其成员

  4. The following output describes one of the features that displays the general structure of a GeoJSON file. The properties of interest for us are the NAME, Obesity, and the geometry coordinates:

    Figure 5.10: General structure of the geojson file

    图 5.10:geojson 文件的一般结构

    注意

    地理空间应用更喜欢 GeoJSON 文件来保存和交换地理数据。

  5. Depending on the information present in the GeoJSON file, we might need to extract some of it for later mappings. For the obesity database, we want to extract the names of all the states of the US. The following code does the same:

    # listing the states in the dataset
    with open('data/National_Obesity_By_State.geojson') as data:
        dataset = json.load(data)
        states = [feature['properties']['NAME'] for feature in dataset.get('features')]
        print(states)
    

    下图显示了前面代码的输出:

    Figure 5.11: List of all cities in the United States

    图 5.11:美国所有城市列表
  6. If your GeoJSON file is valid, meaning that it has the expected structure, you can then use the geojson method of Geoplotlib. By only providing the path to the file, it will plot the coordinates for each feature in a blue color by default:

    # plotting the information from the geojson file
    geoplotlib.geojson('data/National_Obesity_By_State.geojson')
    geoplotlib.show()
    

    调用show方法后,地图将显示北美。在下图中,我们已经可以看到每个州的边界:

    Figure 5.12: Map with outlines of the states plotted

    图 5.12:绘制了各州轮廓的地图
  7. 要指定一种代表每个状态肥胖的颜色,我们必须为geojson方法提供color参数。我们可以根据用例提供不同的类型。我们不希望给每个州分配一个单一的值,而是希望用黑暗来代表肥胖人口的百分比。为此,我们必须为color房产提供一个方法。我们的方法只是将Obesity属性映射到一个ColorMap类对象,该类对象有足够的级别来进行良好的区分:

    # converting the obesity into a color
    cmap = ColorMap('Reds', alpha=255, levels=40)
    def get_color(properties):
        return cmap.to_color(properties['Obesity'], maxvalue=40,scale='lin')
    
  8. 然后,我们将颜色映射提供给我们的color参数。然而,这不会填满这些区域。因此,我们还必须将fill参数设置为True。此外,我们还希望保持我们国家的轮廓清晰可见。在这里,我们可以利用geo lotlib是基于图层的概念,所以我们可以简单地再次调用相同的方法,提供白色并将fill参数设置为false。我们还想确保我们的视图显示的是国家USA。为此,我们再次使用 Geoplotlib 提供的常量之一:

    # plotting the shaded states and adding another layer which plots the state outlines in white
    # our BoundingBox should focus the USA
    geoplotlib.geojson('data/National_Obesity_By_State.geojson', fill=True, color=get_color)
    geoplotlib.geojson('data/National_Obesity_By_State.geojson', fill=False, color=[255, 255, 255, 255])
    geoplotlib.set_bbox(BoundingBox.USA)
    geoplotlib.show()
    
  9. 执行上述步骤后,预期输出如下:

Figure 5.13: Choropleth visualization showing obesity in different states

图 5.13:cholopleth 可视化显示不同状态下的肥胖

一个新的窗口将会打开,显示美国这个国家,其各州的区域被不同深浅的红色填满。较暗的区域代表较高的肥胖百分比。

注意

为了给用户更多关于这个图的信息,我们还可以使用f_tooltip参数为每个状态提供一个工具提示,从而显示肥胖人群的名称和百分比。

恭喜你!您已经使用 Geoplotlib 构建了不同的图和可视化。在本练习中,我们观察了显示来自 GeoJSON 文件的数据和创建弦线图

在以下主题中,我们将介绍更高级的自定义,这些自定义将为您提供创建更强大可视化的工具。

瓷砖供应商

Geoplotlib 支持不同图块提供者的使用。这意味着任何开放街道地图平铺服务器都可以作为我们可视化的背景。一些受欢迎的免费瓷砖供应商是雄蕊水彩雄蕊化妆水、雄蕊化妆水 Lite 和暗物质

可以通过两种方式更改切片提供程序:

  • Make use of built-in tile providers

    Geoplotlib 包含一些带有快捷方式的内置切片提供程序。下面的代码显示了如何使用它:

    geoplotlib.tiles_provider('darkmatter')
    
  • Provide a custom object to the tiles_provider method

    通过为 Geoplotlib 的tiles_provider()方法提供一个自定义对象,您不仅可以访问加载地图切片的url,还可以看到可视化右下角显示的attribution。我们还能够为下载的切片设置不同的缓存目录。下面的代码演示如何提供自定义对象:

    geoplotlib.tiles_provider({
    	'url': lambda zoom, xtile, ytile:
    		'http://a.tile.stamen.com/watercolor/%d/%d/%d.png' % (zoom, xtile, ytile),
    	'tiles_dir': 'tiles_dir',
    	'attribution': 'Python Data Visualization | Packt'
    })
    

    tiles_dir中的缓存是强制性的,因为每次滚动或放大地图时,我们都会查询尚未下载的新地图切片。这可能会导致磁贴提供商在短时间内由于许多请求而拒绝您的请求。

在下面的练习中,我们将快速了解如何切换地图切片提供程序。起初,它可能看起来并不强大,但如果利用得当,它可以让你的可视化更上一层楼。

练习 8:直观地比较不同的瓷砖供应商

本快速练习将教您如何为可视化效果切换地图切片提供程序。Geoplotlib 为一些可用且最受欢迎的地图切片提供映射。但是,我们也可以提供一个自定义对象,其中包含一些图块提供者的url:

  1. Open the Jupyter Notebook exercise08.ipynb from the Lesson05 folder to implement this exercise.

    为此,您需要导航到该文件的路径,并在命令行终端中键入:jupyter-lab

  2. 在本练习中,我们将不使用任何数据集,因为我们希望关注地图切片和切片提供者。所以我们唯一需要做的导入就是geoplotlib本身:

    # importing the necessary dependencies
    import geoplotlib
    
  3. We know that Geoplotlib has a layers approach to plotting. This means that we can simply display the map tiles without adding any plotting layer on top:

    # displaying the map with the default tile provider
    geoplotlib.show()
    

    下图显示了前面代码的输出:

    Figure 5.14: World map with default tile provider

    图 5.14:带有默认图块提供者的世界地图

    这将显示一个完全空白的世界地图,因为我们还没有指定任何图块提供者。默认情况下,它将使用 CartoDB 正电子地图切片。

  4. Geoplotlib provides several shorthand accessors to common map tile providers. The tiles_provider method allows us to simply provide the name of the provider:

    # using map tiles from the dark matter tile provider
    geoplotlib.tiles_provider('darkmatter')
    geoplotlib.show()
    

    下图显示了前面代码的输出:

    Figure 5.15: World map with darkmatter map tiles

    图 5.15:带有暗物质地图图块的世界地图

    在本例中,我们使用了darkmatter地图图块。如你所见,这些非常暗,会让你的视觉效果突出。

    注意

    我们也可以以类似的方式使用不同的地图图块,如水彩色粉色粉-lite正电子

  5. When using tile providers that are not covered by geoplotlib, we can pass a custom object to the tiles_provider method. It maps the current viewport information to the url. The tiles_dir parameter defines where the tiles should be cached. When changing the url, you also have to change tiles_dir to see the changes immediately. The attribution gives you the option to display custom text in the right lower corner:

    # using custom object to set up tile provider
    geoplotlib.tiles_provider({
        'url': lambda zoom, xtile, ytile: 'http://a.tile.openstreetmap.fr/hot/%d/%d/%d.png' % (zoom, xtile, ytile),
        'tiles_dir': 'custom_tiles',
        'attribution': 'Custom Tiles Provider - Humanitarian map style | Packt Courseware'
    })
    geoplotlib.show()
    

    下图显示了前面代码的输出:

    Figure 5.16: Humanitarian map tiles from custom tile providers object

图 5.16:来自自定义图块提供者对象的人道主义贴图图块

一些地图切片提供程序有严格的请求限制,因此如果缩放速度过快,可能会导致警告消息。

恭喜你!现在,您已经知道如何更改切片提供程序,从而为可视化增加一层可定制性。这也向我们介绍了另一层复杂性。这完全取决于我们最终产品的概念,以及我们是要使用“默认”地图切片还是一些艺术地图切片。

下一个主题将涵盖如何创建自定义图层,这些图层可以远远超出我们在本书中描述的范围。我们将看看BaseLayer类的基本结构,以及创建自定义图层需要什么。

自定义图层

现在,我们已经介绍了使用内置图层可视化地理空间数据的基础知识,以及更改切片提供程序的方法,现在我们将专注于定义我们自己的自定义图层。自定义图层允许您创建更复杂的数据可视化。它们也有助于增加更多的交互性和动画。创建一个自定义图层首先要定义一个新的类,这个类扩展了 Geoplotlib 提供的BaseLayer类。除了初始化类级变量的__init__方法,我们还必须至少扩展已经提供的BaseLayer类的draw方法。

根据可视化的性质,您可能还想实现使无效的方法,该方法负责地图投影更改,例如放大您的可视化。drawinvalidate方法都接收一个Projection对象,该对象负责二维视口上的经纬度映射。这些映射的点可以交给BatchPainter对象的一个实例,该实例提供诸如点、线和形状等图元,以将这些坐标绘制到地图上。

注意

由于 Geoplotlib 在 OpenGL 上运行,这个过程性能很高,甚至可以快速绘制复杂的可视化。有关如何创建自定义图层的更多示例,请访问 Geoplotlib 的以下 GitHub 存储库:https://GitHub . com/Andrea-cuttone/Geoplotlib/tree/master/examples

活动 28:使用自定义图层

在本练习中,我们将了解如何创建一个自定义图层,该图层不仅允许您显示地理空间数据,还允许您随着时间的推移制作数据点的动画。我们将更深入地了解 Geoplotlib 是如何工作的,以及图层是如何创建和绘制的。我们的数据集不仅包含空间信息,还包含时间信息,这使我们能够在地图上绘制航班随时间的变化:

  1. Lesson05文件夹中打开 Jupyter 笔记本activity28.ipynb执行本活动。
  2. 首先,确保导入必要的依赖项。
  3. 使用pandas加载flight_tracking.csv数据集。
  4. 看一下数据集及其特征。
  5. 因为我们的数据集有命名为LatitudeLongitude的列,而不是latlon,所以将这些列重命名为它们的短版本。
  6. 我们的自定义图层将动画显示飞行数据,这意味着我们需要处理数据的timestamp日期时间是两个独立的栏目,所以我们需要合并这两个栏目。使用提供的to_epoch方法,创建一个新的时间戳列。
  7. 创建一个新的TrackLayer,扩展 Geoplotlib 的BaseLayer
  8. 对于TrackLayer,执行__init__drawbbox的方法。拨打TrackLayer时,使用提供的BoundingBox关注利兹。
  9. 执行上述步骤后,预期输出如下:

Figure 5.17: Flight tracking visualization focused on Leeds

图 5.17:聚焦利兹的飞行跟踪可视化

注意:

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

总结

在这一章中,我们介绍了地质图书馆的基本和先进的概念和方法。它让我们快速了解了内部流程,以及如何将库实际应用到我们自己的问题陈述中。大多数情况下,内置绘图应该非常适合您的需求。一旦你对动画甚至交互式可视化感兴趣,你就必须创建自定义图层来启用这些功能。

在下一章中,我们将获得一些使用 Bokeh 库的实践经验,并构建可以轻松集成到网页中的可视化。一旦我们使用完 Bokeh,我们将以一个章节来结束这个章节,这个章节让你有机会使用一个新的数据集和一个你选择的库,这样你就可以想出你自己的可视化。这将是用 Python 巩固数据可视化之旅的最后一步。

六、让事物与 Bokeh 互动

学习目标

本章结束时,您将能够:

  • 使用 Bokeh 创建有洞察力的基于网络的可视化
  • 解释两种绘图界面的区别
  • 确定何时使用 Bokeh 服务器
  • 创建交互式可视化

在本章中,我们将使用 Bokeh 库设计交互式图。

简介

Bokeh 从 2013 年就有了,2018 年发布了 1.0.4 版本。它的目标是现代网络浏览器向用户呈现交互式可视化,而不是静态图像。以下是 Bokeh 的一些特点:

  • 简单可视化:通过其不同的界面,针对多种技能水平的用户,从而为快速简单的可视化提供了一个 API,但也提供了更复杂且极具可定制性的可视化。
  • 出色的动画可视化:它提供了高性能,因此可以处理大型甚至流式数据集,这使得它成为动画可视化和数据分析的首选。
  • 交互可视化交互:这是一种基于网络的方法,可以很容易地将几个绘图结合起来,创建独特而有影响力的仪表板,并带有可以相互连接的可视化,以创建交互可视化交互。
  • 支持多种语言:除了 Matplotlib 和 geoplotlib,Bokeh 不仅有 Python 的库,还有 JavaScript 本身的库,以及其他几种流行的语言。
  • 执行任务的多种方式:前面提到的交互性可以通过多种方式添加。最简单的内置方式是可以在可视化中缩放和平移,这已经让用户可以更好地控制他们想要看到的内容。除此之外,我们还可以授权用户过滤和转换数据。
  • 漂亮的图表造型:技术栈基于后端的 Tornado,由前端的 D3 提供动力,解释了图表漂亮的默认造型。

由于我们在整本书中都在使用 Jupyter Notebook,值得一提的是,Bokeh,包括它的交互性,在 Notebook 中是原生支持的。

Bokeh 概念

Bokeh 的基本概念在某些方面可以与 Matplotlib 相媲美。在 Bokeh 中,我们有一个图形作为根元素,它有子元素,如标题、轴和字形。字形必须添加到图形中,图形可以采用不同的形状,如圆形、条形和三角形来表示图形。以下层次结构显示了 Bokeh 的不同概念:

图 6.1:Bokeh 概念

Bokeh 的界面

基于界面的方法为用户提供了不同程度的复杂性,这些用户要么只是想要创建一些具有极少可定制参数的基本图,要么想要完全控制其可视化并想要定制其图的每个元素。这种分层方法分为两个级别:

  • 标绘:此图层可自定义。

  • Models interface: This layer is complex and provides an open approach to designing charts.

    注意

    模型界面是所有绘图的基本构件。

以下是接口中分层方法的两个级别:

  • bokeh.plotting

    这个中级接口有一个类似于 Matplotlib 的 API。工作流程是创建一个图形,然后用不同的字形丰富这个图形,这些字形在图形中呈现数据点。与 Matplotlib 类似,轴、网格和检查器等子元素的合成(它们提供了通过缩放、平移和悬停来浏览数据的基本方式)无需额外配置即可完成。

    这里需要注意的重要一点是,即使它的设置是自动完成的,我们也能够配置这些子元素。使用该界面时, BokehJS 使用的场景图的创建也是自动处理的。

  • bokeh.models

    这个低级接口由两个库组成:称为 BokehJS 的 JavaScript 库,用于在浏览器中显示图表,以及提供开发人员界面的 Python 库。在内部,Python 中创建的定义创建了 JSON 对象,这些对象保存了浏览器中 JavaScript 表示的声明。

    模型界面展示了对 Bokeh 绘图和小部件(使用户能够与显示的数据交互的元素)的组装和配置的完全控制。这意味着开发人员有责任确保场景图(描述可视化的对象集合)的正确性。

输出

输出 Bokeh 图很简单。根据您的需求,有三种方法可以做到这一点:

  • .show()方法:基本选项是在一个 HTML 页面中简单显示绘图。这是通过。show()方法。
  • 内联.show()方法:使用 Jupyter Notebook 时,.show()方法将允许您在笔记本内显示图表(使用内联绘图时)。
  • .output_file()方法:您也可以使用.output_file()方法直接将可视化保存到文件中,而没有任何开销。这将在给定的路径上用给定的名称创建一个新文件。

提供可视化的最有力的方法是使用 Bokeh 服务器。

Bokeh 服务器

正如我们之前提到的,Bokeh 创建场景图 JSON 对象,BokehJS 库将解释这些对象以创建可视化输出。此过程允许您为其他语言创建统一的格式,以创建相同的 Bokeh 图和可视化效果,与所使用的语言无关。

如果我们想得更远一点,如果我们也能保持可视化彼此同步呢?这将使我们能够创建更复杂的可视化方式,并利用 Python 提供的工具。我们不仅可以过滤数据,还可以在服务器端进行计算和操作,这将实时更新可视化。

除此之外,由于我们有了一个数据入口点,我们可以创建由流而不是静态数据集提供的可视化。这种设计使我们能够拥有功能更强大的更复杂的系统。

看一下这个架构的方案,我们可以看到文档是在服务器端提供的,然后转移到浏览器客户端,浏览器客户端再将其插入到 BokehJS 库中。这个插入将触发 BokehJS 的解释,然后 BokehJS 将创建可视化本身。下图描述了博凯服务器的工作情况:

Figure 6.2: Workings of the Bokeh Server

图 6.2:Bokeh 服务器的工作原理

演示

在 Bokeh 中,演示文稿通过使用不同的功能,如交互、样式、工具和布局,帮助使可视化更具交互性。

互动

Bokeh 最有趣的特点可能是它的交互。基本上有两种交互方式:被动主动

被动交互是用户可以采取的既不改变数据也不改变显示数据的动作。在 Bokeh,这被称为检查员。正如我们之前提到的,检查器包含缩放、平移和悬停在数据上等属性。这种工具允许用户进一步检查其数据,并且可能通过只查看可视化数据点的放大子集来获得更好的见解。

主动交互是直接改变显示数据的动作。这包括选择数据子集或基于参数过滤数据集等操作。小部件是最突出的活跃交互,因为它们允许用户简单地用处理程序操作显示的数据。小部件可以是按钮、滑块和复选框等工具。回到关于输出样式的小节,这些小部件可以在两者中使用——所谓的独立应用和 Bokeh 服务器。这将有助于我们巩固最近学习的理论概念,使事情更加清晰。Bokeh 中的一些交互是选项卡窗格、下拉列表、多选、单选按钮组、文本输入、检查按钮组、数据表和滑块。

积分

嵌入 Bokeh 可视化有两种形式,如下所示:

HTML 文档:这些是独立的 HTML 文档。这些文件非常完备。

Bokeh 应用:它们由 Bokeh Server 支持,这意味着它们提供了连接的可能性,例如:用于更高级可视化的 Python 工具。

与带有 Seaborn 的 Matplotlib 相比,Bokeh 有点复杂,并且像其他库一样有它的缺点,但是一旦您关闭了基本的工作流程,您就能够利用 Bokeh 带来的好处,这些好处是您可以通过简单地添加交互功能并为用户提供动力来扩展视觉表示的方式。

注意

一个有趣的特性是to_bokeh方法,它允许你在没有配置开销的情况下用 Bokeh 绘制 Matplotlib 图形。有关该方法的更多信息,请访问以下链接:https://bokeh . pydata . org/en/0 . 12 . 3/docs/user _ guide/compat . html

在接下来的练习和活动中,我们将巩固理论知识并构建几个简单的可视化来理解 Bokeh 及其两个界面。在我们介绍了基本用法之后,我们将比较绘图和models界面,看看使用它们的区别,并使用向可视化添加交互性的小部件。

注意

本章中的所有练习和活动都是使用 Jupyter 笔记本和 Jupyter 实验室开发的。文件可从以下链接下载:https://bit.ly/2T3Afn1

练习 9:与 Bokeh 一起绘图

在本练习中,我们希望使用更高级别的界面,该界面侧重于为快速可视化创建提供一个简单的界面。参考介绍,用 Bokeh 的不同接口进行回查。在本练习中,我们将使用world_population数据集。这个数据集显示了多年来不同国家的人口。我们将使用绘图界面深入了解德国和瑞士的人口密度:

  1. Lesson06文件夹打开exercise09_solution.ipynb Jupyter 笔记本,执行本练习。为此,您需要在命令行终端中导航到该文件的路径,并键入jupyter-lab.

  2. 如前所述,我们将在本练习中使用绘图界面。我们必须从绘图中导入的唯一元素是图形(将初始化绘图)和show方法(显示绘图):

    # importing the necessary dependencies
    import pandas as pd
    from bokeh.plotting import figure, show
    
  3. 需要注意的一点是,如果我们想在 Jupyter 笔记本中显示我们的绘图,我们还必须从 Bokeh 的io界面导入并调用output_notebook方法:

    # make bokeh display figures inside the notebook
    from bokeh.io import output_notebook
    output_notebook()
    
  4. 用熊猫加载我们的world_population数据集:

    # loading the dataset with pandas
    dataset = pd.read_csv('./data/world_population.csv', index_col=0)
    
  5. A quick test by calling head on our DataFrame shows us that our data has been successfully loaded:

    # looking at the dataset
    dataset.head()
    

    下图显示了前面代码的输出:

    Figure 6.3: Loading the top 5 rows of the world_population dataset using he head method

    图 6.3:使用 head 方法加载世界人口数据集的前五行
  6. 为了填充我们的 x 轴和 y 轴,我们需要做一些数据提取。x 轴将包含我们专栏中的所有年份。y 轴将保存各国的人口密度值。我们从德国开始:

    # preparing our data for Germany
    years = [year for year in dataset.columns if not year[0].isalpha()] de_vals = [dataset.loc[['Germany']][year] for year in years]
    
  7. After extracting the wanted data, we can create a new plot by calling the Bokeh figure method. By providing parameters such as title, x_axis_label, and y_axis_label, we can define the descriptions displayed on our plot. Once our plot is created, we can add glyphs to it. In our example, we will use a simple line.By providing the legend parameter next to the x and y values, we get an informative legend in our visualization:

    # plotting the population density change in Germany in the given years
    plot = figure(title='Population Density of Germany', x_axis_label='Year', y_axis_label='Population Density')
    plot.line(years, de_vals, line_width=2, legend='Germany')
    show(plot)
    

    下图显示了前面代码的输出:

    Figure 6.4: Creating a line plot from population density data of Germany

    图 6.4:根据德国的人口密度数据创建线图
  8. 我们现在想添加另一个国家。在这种情况下,我们将使用Switzerland。我们将使用与Germany相同的技术提取Switzerland :

    # preparing the data for the second country
    ch_vals = [dataset.loc[['Switzerland']][year] for year in years]
    

    的数据

  9. We can simply add several layers of glyphs on to our figure plot. We can also stack different glyphs on top of one another, thus giving specific data-improved visuals. In this case, we want to add an orange line to our plot that displays the data from Switzerland. In addition to that, we also want to have circles for each entry in the data, giving us a better idea about where the actual data points reside. By using the same legend name, Bokeh creates a combined entry in the legend:

    # plotting the data for Germany and Switzerland in one visualization, 
    # adding circles for each data point for Switzerland
    plot = figure(title='Population Density of Germany and Switzerland', x_axis_label='Year', y_axis_label='Population Density')
    plot.line(years, de_vals, line_width=2, legend='Germany')
    plot.line(years, ch_vals, line_width=2, color='orange', legend='Switzerland')
    plot.circle(years, ch_vals, size=4, line_color='orange', fill_color='white', legend='Switzerland')
    show(plot)
    

    下图显示了前面代码的输出:

    Figure 6.5: Adding Switzerland to the plot

    图 6.5:将瑞士添加到图中
  10. When looking at a larger amount of data for different countries, it makes sense to have a plot for each of them separately. This can be achieved by using one of the layout interfaces. In this case, we are using the gridplot:

```py
# plotting the Germany and Switzerland plot in two different visualizations
# that are interconnected in terms of view port
from bokeh.layouts import gridplot
plot_de = figure(title='Population Density of Germany', x_axis_label='Year', y_axis_label='Population Density', plot_height=300)
plot_ch = figure(title='Population Density of Switzerland', x_axis_label='Year', y_axis_label='Population Density', plot_height=300, x_range=plot_de.x_range, y_range=plot_de.y_range)
plot_de.line(years, de_vals, line_width=2)
plot_ch.line(years, ch_vals, line_width=2)
plot = gridplot([[plot_de, plot_ch]])
show(plot)
```

下图显示了前面代码的输出:

![Figure 6.6: Using a gridplot to display the country plots next to each other](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_06_06.jpg)

###### 图 6.6:使用网格点显示相邻的国家绘图
  1. As the name suggests, we can arrange the plots in a grid. This also means that we can quickly get a vertical display when changing the two-dimensional list that was passed to the gridplot method:
```py
# plotting the above declared figures in a vertical manner
plot_v = gridplot([[plot_de], [plot_ch]])
show(plot_v)
```

下面的屏幕截图显示了前面代码的输出:

Figure 6.7: Using the gridplot method to arrange the visualizations vertically

图 6.7:使用 gridplot 方法垂直排列可视化效果

恭喜你!我们刚刚介绍了 Bokeh 的基础知识。使用plotting界面可以很容易地快速可视化。这确实有助于您理解正在处理的数据。

然而,这种简单是通过抽象出复杂性来实现的。我们通过使用plotting界面失去了很多控制。在下一个练习中,我们将比较plottingmodels界面,向您展示plotting增加了多少抽象。

练习 10:比较绘图和模型界面

在本练习中,我们要比较两个界面:绘制模型。我们将通过使用高级绘图界面创建基本绘图来比较它们,然后使用低级模型界面重新创建该绘图。这将向我们展示这两个界面之间的差异,并为我们后面的练习提供一个很好的方向,以了解如何使用models界面:

  1. Lesson06打开 Jupyter 笔记本exercise10_solution.ipynb进行本练习。为此,您需要再次导航到该文件的路径。在命令行终端中,键入jupyter-lab

  2. 如前所述,本练习将使用plotting界面。我们必须从绘图中导入的唯一元素是figure(将初始化绘图)和show方法(显示绘图):

    # importing the necessary dependencies
    import numpy as np
    import pandas as pd
    
  3. 需要注意的一点是,如果我们想在 Jupyter 笔记本中显示我们的绘图,我们还必须从 Bokeh 的io界面导入并调用output_notebook方法:

    # make bokeh display figures inside the notebook
    from bokeh.io import output_notebook
    output_notebook()
    
  4. 就像我们之前做的几次一样,我们将使用熊猫来加载我们的world_population数据集:

    # loading the dataset with pandas
    dataset = pd.read_csv('./data/world_population.csv', index_col=0)
    
  5. A quick test by calling head on our DataFrame shows us that our data has been successfully loaded:

    # looking at the dataset
    dataset.head()
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.8: Loading the top 5 rows of the world_population dataset using the head method

    图 6.8:使用 head 方法加载世界人口数据集的前五行
  6. 在这部分练习中,我们将使用之前看到的plotting界面。正如我们之前看到的,我们基本上只需要导入figure来创建,show来显示我们的绘图:

    # importing the plotting dependencies 
    from bokeh.plotting import figure, show
    
  7. 我们的数据在两个图中都保持不变,因为我们只想改变我们创建可视化的方式。我们需要以列表的形式显示数据集中的年份、整个数据集每年的平均人口密度以及Japan :

    # preparing our data of the mean values per year and Japan
    years = [year for year in dataset.columns if not year[0].isalpha()]
    mean_pop_vals = [np.mean(dataset[year]) for year in years]
    jp_vals = [dataset.loc[['Japan']][year] for year in years]
    

    的每年平均人口密度

  8. When using the plotting interface, we can create a plot element that gets all the attributes about the plot itself, such as the title and axis labels. We can then use the plot element and "apply" our glyphs elements to it. In this case, we will plot the global mean with a line and the mean of Japan with crosses:

    # plotting the global population density change and the one for Japan 
    plot = figure(title='Global Mean Population Density compared to Japan', x_axis_label='Year', y_axis_label='Population Density')
    plot.line(years, mean_pop_vals, line_width=2, legend='Global Mean')
    plot.cross(years, jp_vals, legend='Japan', line_color='red')
    show(plot)
    

    下面的屏幕截图显示了前面代码的输出:

图 6.9:全球平均人口密度与日本人口密度的对比线图

正如我们在前面的图表中看到的,我们已经有了许多元素。这意味着我们已经有了正确的 x 轴标签,y 轴的匹配范围,我们的图例被很好地放置在右上角,没有太多的配置。

使用模型界面

  1. 与其他接口相比,models接口的级别要低得多。我们已经可以看到这一点时,看看进口清单,我们需要一个可比的阴谋。在列表中,我们可以看到一些熟悉的名字,如PlotAxisLineCross :

    # importing the models dependencies 
    from bokeh.io import show
    from bokeh.models.grids import Grid
    from bokeh.models.plots import Plot 
    from bokeh.models.axes import LinearAxis
    from bokeh.models.ranges import Range1d
    from bokeh.models.glyphs import Line, Cross
    from bokeh.models.sources import ColumnDataSource
    from bokeh.models.tickers import SingleIntervalTicker, YearsTicker
    from bokeh.models.renderers import GlyphRenderer
    from bokeh.models.annotations import Title, Legend, LegendItem
    
  2. 在构建我们的图之前,我们必须找出 y 轴的minmax值,因为我们不想有太大或太小的值范围。因此,我们得到全局和Japan的所有平均值,没有任何无效值,然后得到它们的最小值和最大值。然后这些值被传递给Range1d的构造器,它会给我们一个范围,以后可以在绘图构造中使用。对于 x 轴,我们预先定义了我们的年份列表:

    # defining the range for the x and y axis
    extracted_mean_pop_vals = [val for i, val in enumerate(mean_pop_vals) if i not in [0, len(mean_pop_vals) - 1]]
    extracted_jp_vals = [jp_val['Japan'] for i, jp_val in enumerate(jp_vals) if i not in [0, len(jp_vals) - 1]]
    min_pop_density = min(extracted_mean_pop_vals)
    min_jp_densitiy = min(extracted_jp_vals)
    min_y = int(min(min_pop_density, min_jp_densitiy))
    max_pop_density = max(extracted_mean_pop_vals)
    max_jp_densitiy = max(extracted_jp_vals)
    max_y = int(max(max_jp_densitiy, max_pop_density))
    xdr = Range1d(int(years[0]), int(years[-1]))
    ydr = Range1d(min_y, max_y)
    
  3. 一旦我们有了 y 轴的minmax值,我们就可以创建两个Axis对象,用于显示轴线和轴的标签。由于我们还需要不同值之间的刻度,我们必须传入一个Ticker对象,为我们创建这个设置:

    # creating the axis
    axis_def = dict(axis_line_color='#222222', axis_line_width=1, major_tick_line_color='#222222', major_label_text_color='#222222',major_tick_line_width=1)
    x_axis = LinearAxis(ticker = SingleIntervalTicker(interval=10), axis_label = 'Year', **axis_def)
    y_axis = LinearAxis(ticker = SingleIntervalTicker(interval=50), axis_label = 'Population Density', **axis_def)
    
  4. 创建标题和绘图本身很简单。我们可以将一个Title对象传递给Plot对象的标题属性:

    # creating the plot object
    title = Title(align = 'left', text = 'Global Mean Population Density compared to Japan')
    plot = Plot(x_range=xdr, y_range=ydr, plot_width=650, plot_height=600, title=title)
    
  5. If we try to display our plot now using the show method, we will get an error, since we have no renderers defined at the moment. First, we need to add elements to our plot:

    # error will be thrown because we are missing renderers that are created when adding elements
    show(plot)
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.10: Empty plot with title

    图 6.10:带标题的空图
  6. 当处理数据时,我们总是需要将数据插入到数据源对象中。这可用于将数据源映射到将在绘图中显示的字形对象:

    # creating the data display
    line_source = ColumnDataSource(dict(x=years, y=mean_pop_vals))
    line_glyph = Line(x='x', y='y', line_color='#2678b2', line_width=2)
    cross_source = ColumnDataSource(dict(x=years, y=jp_vals))
    cross_glyph = Cross(x='x', y='y', line_color='#fc1d26')
    
  7. 向图中添加对象时,必须使用正确的add方法。对于布局元素,如Axis对象,我们必须使用add_layout方法。显示我们数据的Glyphs必须用add_glyph方法添加:

    # assembling the plot
    plot.add_layout(x_axis, 'below')
    plot.add_layout(y_axis, 'left')
    line_renderer = plot.add_glyph(line_source, line_glyph)
    cross_renderer = plot.add_glyph(cross_source, cross_glyph)
    
  8. If we now try to show our plot, we can finally see that our lines are in place:

    show(plot)
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.11: Models interface-based plot displaying the lines and axes

    图 6.11:显示直线和轴的基于模型界面的图
  9. 仍然缺少一些元素。其中一个是右上角的传说。为了给我们的绘图增加一个传奇,我们又不得不使用一个对象。每个LegendItem对象将在图例中显示为一行:

    # creating the legend
    legend_items= [LegendItem(label='Global Mean', renderers=[line_renderer]), LegendItem(label='Japan', renderers=[cross_renderer])]
    legend = Legend(items=legend_items, location='top_right')
    
  10. 创建网格很简单:我们只需为 x 轴和 y 轴实例化两个Gridobjects。这些网格将获得之前创建的 x 轴和 y 轴的刻度:

```py
# creating the grid
x_grid = Grid(dimension=0, ticker=x_axis.ticker)
y_grid = Grid(dimension=1, ticker=y_axis.ticker)
```
  1. To add the last final touches, we, again, use the add_layout method to add the grid and the legend to our plot. After this, we can finally display our complete plot, which will look like the one we created in the first task, with only four lines of code:
```py
# adding the legend and grids to the plot
plot.add_layout(legend)
plot.add_layout(x_grid)
plot.add_layout(y_grid)
show(plot)
```

下面的屏幕截图显示了前面代码的输出:

Figure 6.12: Full recreation of the visualization done with the plotting interface

图 6.12:用绘图界面完成可视化的完全再现

恭喜你!可以看到,models界面不应该用于简单的绘图。它旨在为有特定需求的经验丰富的用户提供 Bokeh 的全部功能,这些用户需要的不仅仅是plotting界面。之前看过models界面,在我们的下一个话题中会派上用场,这个话题是关于小部件的。

添加小部件

Bokeh 最强大的功能之一是它能够使用小部件来交互更改可视化中显示的数据。为了理解交互性在可视化中的重要性,想象一下看到一个关于股票价格的静态可视化,它只显示去年的数据。如果这是你专门搜索的,那就足够合适了,但如果你有兴趣看到当前年份,甚至视觉上将其与最近几年进行比较,那些绘图就行不通了,会增加额外的工作,因为你必须为每一年创建它们。将它与让用户选择想要的日期范围的简单图进行比较,我们已经可以看到它的优势。有无穷无尽的选项来组合小部件和讲述你的故事。您可以通过限制值并仅显示您希望它们看到的内容来指导用户。开发可视化背后的故事非常重要,如果用户有与数据交互的方式,那么这样做就容易得多。

Bokeh 小部件与 Bokeh 服务器结合使用时效果最佳。然而,使用 Bokeh 服务器方法超出了本书的内容,因为我们需要处理简单的 Python 文件,并且不能利用 Python 笔记本的功能。相反,我们将使用仅适用于旧的 Jupyter 笔记本的混合方法。

练习 11:基本交互小部件

添加小部件主题的第一个练习将向您温和地介绍不同的小部件,以及如何结合可视化使用它们的一般概念。我们将查看基本的小部件,并构建一个简单的图,显示所选股票的前 25 个数据点。可以通过下拉菜单更改显示的库存。

本练习的数据集是stock_prices数据集。这意味着我们将在一段时间内查看数据。由于这是一个大型且可变的数据集,因此在其上显示和解释不同的小部件(如滑块和下拉菜单)会更容易。该数据集在 GitHub 存储库的数据文件夹中可用;这是它的链接:https://bit.ly/2UaLtSV。

我们将查看不同的可用小部件以及如何使用它们,然后使用其中一个小部件构建一个基本的图。关于如何触发更新,有几个不同的选项,这些选项也将在下面的步骤中解释。下表解释了本练习中涉及的小部件:

Figure 6.13: Some of the basic widgets with examples

图 6.13:一些带有示例的基本小部件
  1. Lesson06文件夹打开exercise11_solution.ipynb Jupyter 笔记本,执行本练习。由于在本例中我们需要使用 Jupyter Notebook,我们将在命令行中键入以下内容:jupyter notebook.

  2. 将打开一个新的浏览器窗口,列出当前目录中的所有文件。点击exercise11_solution.ipynb,将在新的选项卡中打开。

  3. 本练习将首先向您介绍基本的小部件,然后向您展示如何使用它们创建基本的可视化。因此,我们将在整个代码中的合适位置添加更多的导入。为了导入我们的数据集,我们需要熊猫:

    # importing the necessary dependencies
    import pandas as pd
    
  4. 同样,我们希望在 Jupyter 笔记本中显示我们的绘图,因此我们必须从 Bokeh 的io界面导入并调用output_notebook方法:

    # make bokeh display figures inside the notebook
    from bokeh.io import output_notebook
    output_notebook()
    
  5. 下载数据集并移动到本章的数据文件夹后,我们可以导入我们的stock_prices.csv数据:

    # loading the Dataset with geoplotlib
    dataset = pd.read_csv('./data/stock_prices.csv')
    
  6. A quick test by calling head on our DataFrame shows us that our data has been successfully loaded:

    # looking at the dataset
    dataset.head()
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.14: Loading the top 5 rows of the stock_prices dataset using the head method

    图 6.14:使用 head 方法加载 stock_prices 数据集的前五行
  7. 由于日期列没有关于小时、分钟和秒的信息,我们希望避免稍后在可视化中显示它们,而只显示年、月和日。因此,我们将创建一个保存日期值的格式化短版本的新列。请注意,单元格的执行将需要一些时间,因为它是一个相当大的数据集。请耐心等待:

    # mapping the date of each row to only the year-month-day format
    from datetime import datetime
    def shorten_time_stamp(timestamp):
        shortened = timestamp[0]
        if len(shortened) > 10:
            parsed_date=datetime.strptime(shortened, '%Y-%m-%d %H:%M:%S')
            shortened=datetime.strftime(parsed_date, '%Y-%m-%d')
        return shortened
    dataset['short_date'] = dataset.apply(lambda x: shorten_time_stamp(x), axis=1)
    
  8. Taking another look at our updated dataset, we can see a new column called short_date that holds the date without the hour, minute, and second information:

    # looking at the dataset with shortened date
    dataset.head()
    

    下面的屏幕截图显示了前面代码的输出:

Figure 6.15: Dataset with the added short_date column

图 6.15:添加了short_date列的数据集

查看基本部件

  1. 在第一个任务中,交互小部件添加了 IPython 的交互元素。我们必须特别导入它们:

    # importing the widgets
    from ipywidgets import interact, interact_manual
    
  2. We'll be using the "syntactic sugar" way of adding a decorator to a method, that is, by using annotations. This will give us an interactive element that will be displayed below the executable cell. In this example, we'll simply print out the result of the interactive element:

    # creating a checkbox
    @interact(Value=False)
    def checkbox(Value=False):
    print(Value)
    

    下面的屏幕截图显示了前面代码的输出:

    图 6.16:如果选中,交互式复选框将从假切换到真

    注意

    @interact()被称为装饰器,它将带注解的方法包装到交互组件中。这允许我们显示下拉菜单的变化并做出反应。每当下拉列表的值发生变化时,就会执行该方法。

  3. 一旦我们有了第一个元素,所有其他元素都是以完全相同的方式创建的,只是改变了装饰器中参数的数据类型。

  4. Use the following code for the dropdown:

    # creating a dropdown
    options=['Option1', 'Option2', 'Option3', 'Option4']
    @interact(Value=options)
    def slider(Value=options[0]):
    print(Value)
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.17: Interactive dropdown

    图 6.17:交互式下拉列表

    对输入文本使用以下代码:

    # creating an input text
    @interact(Value='Input Text')
    def slider(Value):
    print(Value)
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.18: Interactive text input

    图 6.18:交互式文本输入

    使用以下代码应用多个小部件:

    # multiple widgets with default layout
    options=['Option1', 'Option2', 'Option3', 'Option4']
    @interact(Select=options, Display=False)
    def uif(Select, Display):
    print(Select, Display)
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.19: Two widgets are displayed vertically by default

    图 6.19:默认情况下,垂直显示两个小部件

    使用以下代码应用 int 滑块:

    # creating an int slider with dynamic updates
    @interact(Value=(0, 100))
    def slider(Value=0):
    print(Value)
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.20: Interactive int slider

    图 6.20:交互式 int 滑块

    使用以下代码应用一个在释放鼠标时触发的 int 滑块:

    # creating an int slider that only triggers on mouse release
    from ipywidgets import IntSlider
    slider=IntSlider(min=0, max=100, continuous_update=False)
    @interact(Value=slider)
    def slider(Value=0.0):
    print(Value)
    

    下面的屏幕截图显示了前面代码的输出:

    Figure 6.21: Interactive int slider that only triggers on mouse release

    图 6.21:只在鼠标释放时触发的交互式 int 滑块

    注意:

    虽然图 6.20 和 6.21 的输出看起来是一样的,但在图 6.21 中,滑块仅在鼠标释放时触发。

  5. If we don't want to update our plot every time we change our widget, we can also use the interact_manual decorator, which adds an execution button to the output:

    # creating a float slider 0.5 steps with manual update trigger
    @interact_manual(Value=(0.0, 100.0, 0.5))
    def slider(Value=0.0):
    print(Value)
    

    下面的屏幕截图显示了前面代码的输出:

Figure 6.22: Interactive int slider with a manual update trigger

图 6.22:带有手动更新触发器的交互式 int 滑块

注意

与之前的单元格相比,这个单元格包含interact_manual装饰器,而不是交互。这将添加一个执行按钮,它将触发值的更新,而不是每次更改都触发。这在处理较大的数据集时非常有用,因为重新计算的时间会很长。因此,您不想为每一个小步骤触发执行,而只想在选择了正确的值后触发执行。

创建基本图并添加部件

在本任务中,我们将使用股价数据集创建一个基本的可视化。这将是你的第一次交互式可视化,你可以动态改变显示在图表中的股票。我们将习惯于前面提到的交互小部件之一:下拉菜单。这将是我们可视化交互的要点:

  1. 为了能够创建绘图,我们首先需要从绘图界面导入已经熟悉的图形和显示方法。由于我们也希望有一个面板有两个选项卡显示不同的绘图风格,我们还需要models界面中的PanelTabs类:

    # importing the necessary dependencies 
    from bokeh.models.widgets import Panel, Tabs
    from bokeh.plotting import figure, show
    
  2. 为了更好地构建我们的笔记本,我们想编写一个适应性强的方法,获取股票数据的一个子部分作为参数,并构建一个双标签Pane对象,让我们在可视化中的两个视图之间切换。第一个选项卡将包含给定数据的折线图,而第二个选项卡将包含相同数据的基于圆的表示。一个图例将显示当前查看股票的名称:

    # method to build the tab-based plot
        def get_plot(stock):
    stock_name=stock['symbol'].unique()[0]
    line_plot=figure(title='Stock prices', 
    x_axis_label='Date', x_range=stock['short_date'],
    y_axis_label='Price in $USD')
    line_plot.line(stock['short_date'], stock['high'], legend=stock_name)
    line_plot.xaxis.major_label_orientation = 1
    circle_plot=figure(title='Stock prices', x_axis_label='Date', x_range=stock['short_date'], y_axis_label='Price in $USD')
    circle_plot.circle(stock['short_date'], stock['high'], legend=stock_name)
    circle_plot.xaxis.major_label_orientation = 1
    line_tab=Panel(child=line_plot, title='Line')
    circle_tab=Panel(child=circle_plot, title='Circles')
    tabs = Tabs(tabs=[ line_tab, circle_tab ])
    return tabs
    
  3. 在我们建立交互之前,我们必须获得数据集中所有股票名称的列表。一旦我们这样做了,我们就可以使用这个列表作为交互元素的输入。随着下拉菜单的每次交互,我们显示的数据将会更新。为了简单起见,我们只想在这个任务中显示每只股票的前 25 个条目。默认情况下,应该显示苹果的股票。它在数据集中的符号是 AAPL :

    # extracing all the stock names
    stock_names=dataset['symbol'].unique()
    
  4. We can now add the drop-down widget in the decorator and call the method that returns our visualization in the show method with the selected stock. This will give us a visualization that is displayed in a pane with two tabs. The first tab will display an interpolated line and the second tab will display the values as circles:

    # creating the dropdown interaction and building the plot
    # based on selection
    @interact(Stock=stock_names)
        def get_stock_for(Stock='AAPL'):
    stock = dataset[dataset['symbol'] == Stock][:25]
    show(get_plot(stock))
    

    下面的屏幕截图显示了前面代码的输出:

Figure 6.23: Line tab with the data of AAPL displayed

图 6.23:显示 AAPL 数据的行选项卡

下面的屏幕截图显示了步骤 16 中的代码输出:

Figure 6.24: Circle tab with the data of AAPL displayed

图 6.24:显示 AAPL 数据的圆形标签

注意

我们已经可以看到每个日期都显示在 x 轴上。如果我们想显示更大的时间范围,我们必须定制 x 轴上的刻度。这可以使用 ticker 对象来完成。

恭喜你!我们刚刚介绍了小部件的基础知识以及如何在 Jupyter Notebook 中使用它们。

注意

如果您想了解更多关于使用小部件的信息,以及在 Jupyter 中可以使用哪些小部件,可以参考这些链接:【https://bit.ly/2Sx9txZ】https://bit.ly/2T4FcM1

活动 29:使用小部件扩展绘图

在这个活动中,你将结合你已经学到的关于 Bokeh 的知识。您还需要在与熊猫合作时获得的技能,以进行额外的数据帧处理。我们将创建一个交互式可视化,让我们探索 2016 年里约奥运会的最终结果。

我们的可视化将显示参与坐标系的每个国家,其中 x 轴代表赢得的奖牌数量,y 轴代表运动员数量。使用交互式小部件,我们将能够过滤掉显示的国家,包括赢得奖牌的最大数量和运动员轴的最大数量。

在选择使用哪种交互性时,有很多选择。我们将专注于只有两个小部件,以使您更容易理解概念。最终,我们将拥有一个可视化工具,允许我们根据各国在奥运会上获得的奖牌和运动员数量对其进行筛选,并将鼠标悬停在单个数据点上时,我们将获得关于每个国家的更多信息:

  1. Lesson06文件夹打开activity29.ipynb Jupyter 笔记本,执行本活动。
  2. 不要忘记使用bokeh.io界面启用到笔记本输出。导入熊猫并加载数据集。确保通过显示数据集的前五个元素来加载数据集。
  3. 从 Bokeh 导入figureshow,从ipywidgets导入interactwidgets开始。
  4. 当开始创建我们的可视化时,我们必须导入我们将要使用的工具。从 Bokeh 导入figureshow,从ipywidgets导入interactwidgets接口。
  5. 提取必要的数据后,我们将设置交互元素。向下滚动,直到到达显示getting the max amount of medals and athletes of all countries的单元格。从数据集中提取这两个数字。
  6. 提取最大奖牌数和运动员数后,创建最大运动员数的IntSlider(垂直方向)和最大奖牌数的IntSlider(水平方向)小部件。
  7. 实施我们的绘图前的最后一步准备是设置@interact方法,它将显示整个可视化。我们将在这里编写的唯一代码是get_plot方法的返回值show,该方法获取所有交互元素值作为参数。
  8. 在实现了修饰方法之后,我们现在可以在我们的笔记本中向上移动并使用get_plot方法。
  9. 首先,我们希望过滤掉我们的国家数据集,该数据集包含所有将运动员放入olympic games的国家。我们需要检查他们的奖牌和运动员数量是否少于或等于我们作为参数传递的最大值。
  10. 一旦过滤掉数据集,我们就可以创建数据源了。该数据源将用于工具提示和圆形标志符号的打印。
  11. 之后,我们将使用图形方法创建一个新的图,该图具有以下属性:标题为Rio Olympics 2016 - Medal comparison,x_axis_label 为Number of Medals,y_axis_label 为Num of Athletes.
  12. 最后一步是执行从get_plot单元格开始到底部的每个单元格,再次确保所有实现都被捕获。
  13. When executing the cell that contains the @interact decorator, you will see the scatter plot that displays a circle for every country displaying additional information such as the short code of the country, the amount of athletes, and the number of gold, silver, and bronze medals.
#### 注意:

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

总结

在这一章中,我们看到了另一个以全新的焦点创建可视化的选项:基于网络的 Bokeh 图。我们还发现了一些方法,可以让我们的可视化更具互动性,真正给用户一个以完全不同的方式探索数据的机会。正如我们在本章第一部分中提到的,Bokeh 是一个相对较新的工具,它使开发人员能够使用他们最喜欢的语言来创建易于移植的网络可视化。在使用了 Matplotlib、Seaborn、geoplotlib 和 Bokeh 之后,我们可以看到一些公共接口和使用这些库的类似方式。了解了本书中介绍的工具后,理解新的绘图工具将变得简单。

在下一章,也是最后一章,我们将介绍一个新的、尚未覆盖的数据集,您将使用它来创建可视化。最后一章将让你巩固你在本书中学到的概念和工具,并进一步提高你的技能。

七、结合我们所学的知识

学习目标

本章结束时,您将能够:

  • 将你的技能应用于马特洛特利和 Seaborn
  • 用 Bokeh 创建时间序列
  • 使用地理数据库分析地理空间数据

在这一章中,我们将应用我们在前面所有章节中学到的所有概念。我们将结合 Matplotlib、Seaborn、geoplotlib 和 Bokeh 的实际活动使用三个新数据集。我们将以一个总结来结束这一章,这个总结概括了我们在整本书中学到的东西。

简介

为了巩固我们所学的内容,我们将为您提供三项复杂的活动。每项活动都使用我们在本书中介绍过的一个库。每项活动都有比我们在本书中使用的更大的数据集,这将为您准备更大的数据集。

注意

所有活动都将在 Jupyter 笔记本或 Jupyter 实验室中开发。请从https://bit.ly/2SswjqE下载带有所有准备好的模板的 GitHub 资源库。

活动 30:在纽约市数据库上实现 Matplotlib 和 Seaborn

在本活动中,我们将可视化关于纽约市的数据,并将其与纽约州和美国进行比较。使用来自https://www . census . gov/programs-surveys/ACS/technical-documentation/pums/documentation . 2017 . html的美国社区调查(ACS)公共使用微数据样本(PUMS)数据集(从 2017 年开始为期一年的估计)。对于此活动,您可以使用 Matplotlib、Seaborn 或两者的组合。

在本活动中,使用了数据集“纽约人口记录”(./data/pny.csv)和“纽约住房单元记录”(./data/hny.csv)。第一个数据集包含关于纽约人口的信息,第二个数据集包含关于住房单元的信息。数据集包含大约 1%的人口和住房单元的数据。由于数据量庞大,我们不提供整个美国的数据集;相反,如有必要,我们将提供与美国相关的所需信息。PUMS_Data_Dictionary_2017.pdf PDF 提供了所有变量的概述和描述。在ACSPUMS2017CodeLists.xls中可以找到代码的进一步描述:

  1. Lesson07文件夹打开activity30.ipynb Jupyter 笔记本,执行本活动。

  2. 用熊猫来阅读两者。位于子目录data的 csv 文件。

  3. 使用给定的 PUMA(基于 2010 年人口普查定义的公共使用微数据区域代码,即人口超过 10 万的区域)范围,进一步将数据集划分为纽约市各区(布朗克斯、曼哈滕、斯塔滕岛、布鲁克林和皇后区):

    # PUMA ranges
    bronx = [3701, 3710]
    manhatten = [3801, 3810]
    staten_island = [3901, 3903]
    brooklyn = [4001, 4018]
    queens = [4101, 4114]
    nyc = [bronx[0], queens[1]]
    
  4. 在数据集中,每个样本都有一个特定的weight,它反映了整个数据集的weight。因此,我们不能简单地计算中位数。使用下面代码中给定的weighted_median函数计算中位数:

    # Function for a 'weighted' median
    def weighted_frequency(values, weights):
      weighted_values = []
      for value, weight in zip(values, weights):
        weighted_values.extend(np.repeat(value, weight))
      return weighted_values
    def weighted_median(values, weights):
      return np.median(weighted_frequency(values, weights))
    
  5. In this subtask, we will create a plot containing multiple subplots that visualize information with regard to NYC wages. Visualize the median household income for the US, New York, New York City, and its districts. Visualize the average wage by gender for the given occupation categories for the population of NYC:

    occ_categories = ['Management,\nBusiness,\nScience,\nand Arts\nOccupations', 'Service\nOccupations',
                     'Sales and\nOffice\nOccupations', 'Natural Resources,\nConstruction,\nand Maintenance\nOccupations',
                     'Production,\nTransportation,\nand Material Moving\nOccupations']
    occ_ranges = {'Management, Business, Science, and Arts Occupations': [10, 3540], 'Service Occupations': [3600, 4650], 
                     'Sales and Office Occupations': [4700, 5940], 'Natural Resources, Construction, and Maintenance Occupations': [6000, 7630], 
                     'Production, Transportation, and Material Moving Occupations': [7700, 9750]}
    

    想象一下纽约和纽约的工资分配。使用以下年度工资间隔:0 到 100k 之间的 10k 级,100k 到 200k 之间的 50k 级,以及> 200k。

  6. 使用树形图可视化纽约市人口中给定职业子类别的百分比:

    occ_subcategories = {'Management,\nBusiness,\nand Financial': [10, 950],
                        'Computer, Engineering,\nand Science': [1000, 1965],
                        'Education,\nLegal,\nCommunity Service,\nArts,\nand Media': [2000, 2960],
                        'Healthcare\nPractitioners\nand\nTechnical': [3000, 3540],
                        'Service': [3600, 4650],
                        'Sales\nand Related': [4700, 4965],
                        'Office\nand Administrative\nSupport': [5000, 5940],
                        '': [6000, 6130],
                        'Construction\nand Extraction': [6200, 6940],
                        'Installation,\nMaintenance,\nand Repair': [7000, 7630],
                        'Production': [7700, 8965],
                        'Transportation\nand Material\nMoving': [9000, 9750]}
    
  7. Use a heatmap to show the correlation between difficulties (self-care difficulty, hearing difficulty, vision, difficulty, independent living difficulty, ambulatory difficulty, veteran service-connected disability, and cognitive difficulty) and age groups (<5, 5-11, 12-14, 15-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65-74, and 75+) in New York City.

    注意:

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

Bokeh

股票价格数据是许多人最感兴趣的数据类型之一。当思考它的本质时,我们可以看到它是高度动态和不断变化的。为了理解它,我们需要高度的交互性,不仅要看感兴趣的股票,还要比较不同的股票,看它们的交易量,以及给定日期的高点/低点,以及前一天是上涨还是下跌。

考虑到上述所有特性,我们需要使用高度可定制的可视化工具。我们还需要添加不同的小部件来实现交互性。因此,在本活动中,我们将使用 Bokeh 创建一个带有几个交互小部件的蜡烛柱可视化,以便更好地探索我们的数据。

活动 31:可视化 Bokeh 股价

这个活动将结合你已经学到的关于 Bokeh 的大部分知识。你还需要你在和熊猫一起工作时获得的技能。我们将创建一个显示烛台图的交互式可视化,这在处理股票价格数据时经常使用。我们将能够通过从下跌中选择两只股票来相互比较。范围滑块将允许我们在请求的 2016 年限制显示的日期范围。根据我们选择的图表,我们将看到烛台可视化或显示所选股票体积的简单线图:

  1. Lesson07文件夹打开activity31.ipynb Jupyter 笔记本,执行本活动。
  2. 不要忘记使用bokeh.io界面启用笔记本输出。导入熊猫并加载下载的数据集。确保通过显示数据集的前五个元素来加载数据集。
  3. 我们需要在数据框中创建一个列,保存日期列中的信息,而不包含小时、分钟和秒钟信息。通过显示更新后的数据框的前五个元素,确保您的实现工作正常。
  4. 在选择使用哪种交互性时,有很多选择。由于本活动的目标是能够根据交易量以及一段时间范围内的高/低和开盘价/收盘价来比较两只股票,因此我们需要小部件来选择元素,并需要一个滑块来选择给定的范围。考虑到我们有两个显示选项,我们还需要一种方法来选择其中之一。
  5. 从 Bokeh 导入figureshow,从ipywidgets导入interactwidgets开始。
  6. 当开始创建我们的可视化时,我们必须导入我们将要使用的工具。从 Bokeh 导入figureshow,从ipywidgets导入interactwidgets界面。
  7. 从上到下执行单元格,直到到达有注释的单元格#extracting the necessary data.为止,在那里开始执行。从数据集中获取唯一的股票名称。过滤掉 2016 年的日期。只获取 2016 年的唯一日期。创建一个包含字符串open-closevolume的列表,这些字符串将用于单选按钮在两个图之间切换。
  8. 提取必要的数据后,我们将设置交互元素。为以下内容创建小部件:第一个股票名称的下拉列表(默认值为AAPL);将与第一个股票名称进行比较的第二个股票名称的下拉列表(默认值为AON);SelectionRangeSlider选择我们想要在图中显示的日期范围(显示的默认值为 0 到 25);单选按钮,用于在烛台图和显示交易量的图之间进行选择(默认值为open-close,将显示烛台图。)
  9. 实现我们的绘图前最后一步准备就是设置@interact方法,最终显示整个可视化。
  10. 我们这里唯一要写的代码是show获取所有交互元素值作为参数的get_plot方法的返回值。
  11. 在实现了修饰方法之后,我们现在可以在我们的笔记本中向上移动并使用add_candle_plot方法。确保使用 Bokeh 文档中的示例作为大纲。你可以在这个链接中找到相同的内容:https://bokeh . pydata . org/en/latest/docs/gallery/烛台. html
  12. 下一步将继续前进,并在包含get_plot方法的单元格中实现线图。用蓝色为来自stock_1的数据绘制一条线。用橙色为来自stock_2的数据绘制一条线。
  13. 在完成这个活动之前,我们想再增加一个互动功能:屏蔽绘图中的不同元素。这可以通过单击可视化图例中显示的元素之一来完成。当然,我们首先要告诉 Bokeh 它应该怎么做。请在https://bokeh . py data . org/en/latest/docs/user _ guide/interaction/legends . html阅读。
  14. 最后一步是从add_candle_plot单元格开始到底部执行每个单元格,再次确保所有实现都被捕获。
  15. When executing the cell that contains the @interact decorator, you will see the candlestick plot for the two, by default, selected, with the AAPL and AON stocks displayed.
#### 注意:

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

土工联

本次活动中使用的数据集是 Airbnb 的数据集,可在网上公开获取。住宿列表有两个主要特征:纬度和经度。这两个特征让我们能够创建地理空间可视化,让我们更好地了解各种属性,例如每个城市的住宿分布。

因此,在本练习中,我们将使用 geoplotlib 创建一个可视化,将每个住宿映射到地图上的一个点,根据该列表的价格或评级进行着色。这两个属性可以通过按键盘上的左右键来切换。

活动 32:使用 geoplotlib 分析 Airbnb 数据

在 geoplotlib 的最后一个活动中,我们将使用 airbnb 列表数据来确定纽约地区最贵和评级最高的住宿区域。我们将编写一个自定义层,通过它我们可以在价格和每个住宿的评估分数之间进行切换。最终,我们将能够看到全纽约最贵、最优惠的住宿热点。

理论上来说,我们越接近曼哈顿中心,价格就应该会上涨。随着我们越来越靠近曼哈顿市中心,我们将很有兴趣了解给定住宿的收视率是否也会增加:

  1. Lesson07文件夹打开activity32.ipynb Jupyter 笔记本,执行本活动。
  2. 首先,确保导入必要的依赖项。
  3. 使用熊猫加载airbnb_new_york.csv数据集。如果你的系统稍微慢一点,就用数据点少的airbnb_new_york_smaller.csv数据集。
  4. 通过查看数据集,对数据集及其拥有的功能有所了解。
  5. 由于我们的数据集再次拥有名为LatitudeLongitude的列,而不是latlon,因此请将这些列重命名为它们的短版本,这是 geoplotlib 所需要的。
  6. 除此之外,我们还想清理和映射我们的两个主要国家:pricereview_scores_rating。填充n/a值,创建一个名为dollar_price的新列,将价格作为浮动。
  7. 在最终创建图层之前,我们还想减少工作数据集的列数。用idlatitude(作为lat)、longitude(作为lon)、price(在$中)和review_scores_rating创建列的子部分。
  8. 用新创建的数据集子部分创建一个新的DataAccessObject。用它画出一幅点状地图。
  9. 创建一个新的ValueLayer,扩展地理乐库BaseLayer
  10. 在给定数据的情况下,我们希望用当前选定属性定义的颜色绘制地图上的每个点,可以是pricerating
  11. 为了给每个点分配不同的颜色,我们只需分别绘制每个点。这肯定不是最有效的解决方案,但目前可以。
  12. 我们将需要以下实例变量:self.data,它保存数据集;self.display,保存当前选中的属性名称;self.painter,拥有BatchPainter类的实例;self.view,拥有BoundingBoxself.cmap,保存有带有jet颜色模式的颜色图;以及255100级别的α。
  13. 执行ValueLayer__init__invalidatedrawbbox方法。
  14. Use the provided BoundingBox that's focused on New York when calling the ValueLayer.
#### 注意:

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

总结

这一章在三个广泛的实践活动的基础上,给了我们一个简短的概述和本书涵盖的所有内容的回顾。在第 1 章,数据可视化和数据探索的重要性中,我们从一个 Python 库之旅开始,我们将它作为贯穿整本书的指南。我们首先讨论了数据的重要性,并可视化这些数据以获得有意义的见解,然后简要回顾了不同的统计概念。在几个活动中,我们学习了如何使用 Numpy 和熊猫导入和处理数据集。在第二章,关于图你所需要知道的,我们讨论了各种可视化图/图表,以及哪些可视化最适合显示某些信息。我们提到了每种绘图类型的用例、设计实践和实际例子。

第 3 章,深入 Matplotlib 中,我们全面介绍了 Matplotlib,并从基本概念开始。接下来,深入了解了用文本丰富可视化的众多可能性。重点是用实例解释 Matplotlib 提供的几乎所有绘图功能。此外,我们还讨论了创建布局的不同方法。这一章以如何形象化图像和写数学表达式而圆满结束。在第 4 章,使用 Seaborn 简化可视化中,Seaborn 被覆盖,它建立在 Matplotlib 之上,并提供了更高级别的抽象来进行有见地的可视化。通过几个例子,我们向您展示了 Seaborn 如何简化可视化的创建。我们还引入了更多的图,如热图、小提琴图和相关图。最后,我们使用 Squarify 来创建树图。

可视化地理空间数据在第 5 章,使用地理数据库绘制地理空间数据中有所介绍。理解 geoplotlib 的内部结构解释了为什么我们在向可视化添加交互性时必须使用 pyglet 库。在本章的书中,我们使用了不同的数据集,并为地理空间数据构建了静态和交互式可视化。在第 6 章,让事物与 Bokeh 互动中,我们重点关注了与 Bokeh 的合作,Bokeh 的目标是现代网络浏览器呈现互动可视化。从简单的例子开始,我们强调了 Bokeh 最大的优势,即交互小部件。我们用这一章结束了这本书,应用了我们通过使用三个真实数据集所学到的所有技能。

八、附录

关于

包括这一部分是为了帮助学生完成课程中的活动。它包括学生为实现活动目标而要执行的详细步骤。

第 1 章:数据可视化和数据探索的重要性

活动 1:使用 NumPy 计算给定数字的平均值、中值、方差和标准差

解决方案:

让我们使用 NumPy 来计算平均值、中位数、方差和标准差:

  1. 导入必要的库:

    # importing the necessary dependencies
    import numpy as np
    
  2. 使用 NumPy 的genfromtxt方法加载normal_distribution.csv数据集:

    # loading the dataset
    dataset = np.genfromtxt('./data/normal_distribution.csv', delimiter=',')
    
  3. First, we want to print a subset of the first two rows of the dataset:

    # looking at the first two rows of the dataset
    dataset[0:2]
    

    前面代码的输出如下:

    Figure 1.22: First two rows of the dataset

    图 1.22:数据集的前两行
  4. Once we know that our dataset has been successfully loaded, we can start solving our first task, which is calculating the mean of the third row.

    第三行可以通过索引dataset[2]进入:

    # calculate the mean of the third row
    np.mean(dataset[2])
    

    前面代码的输出如下:

    Figure 1.23: Mean of the third row

    图 1.23:第三行的平均值
  5. The last element of an ndarray can be indexed the same way a regular Python List can be accessed. dataset[:, -1] will give us the last column of every row:

    # calculate the mean of the last column
    np.mean(dataset[:,-1])
    

    前面代码的输出如下:

    Figure 1.24: Mean of the last column

    图 1.24:最后一列的平均值
  6. The double-indexing mechanism of NumPy gives us a nice interface to extract sub-selection. In this task, we are asked to get a sub-matrix of the first three elements of every row of the first three columns:

    # calculate the mean of the intersection of the first 3 rows and first 3 columns
    np.mean(dataset[0:3, 0:3])
    

    前面代码的输出如下:

    Figure 1.25: Mean of an intersection

    图 1.25:交叉点的平均值
  7. Moving to the next set of tasks that cover the usage of the median, we will see that the API is consistent between the different methods.:

    # calculate the median of the last row
    np.median(dataset[-1])
    

    前面代码的输出如下:

    Figure 1.26:  Median of the last row

    图 1.26:最后一行的中位数
  8. Reverse indexing also works when defining a range. So, if we want to get the last three columns, we can use dataset[:, -3:]:

    # calculate the median of the last 3 columns
    np.median(dataset[:, -3:])
    

    前面代码的输出如下:

    Figure 1.27: Median of the last 3 columns

    图 1.27:最后 3 列的中位数
  9. As we saw in the previous exercise, we can aggregate the values along an axis. If we want to calculate the rows, we use axis=1:

    # calculate the median of each row
    np.median(dataset, axis=1)
    

    前面代码的输出如下:

    Figure 1.28: Using axis to calculate the median of each row

    图 1.28:使用轴计算每行的中位数
  10. The last method we'll cover here is variance. Again, NumPy provides us with a consistent API, which makes doing this easy. To calculate the variance for each column, we have to use axis 0:

```py
# calculate the variance of each column
np.var(dataset, axis=0)
```

前面代码的输出如下:

![](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_29.jpg)

###### 图 1.29:每列的差异
  1. When only looking at a very small subset of the matrix (2x2) elements, we can apply what we learned in the statistical overview to observe that the value is way smaller than the whole dataset:
#### 注意

数据集的一小部分不显示整体的属性。

```py
# calculate the variance of the intersection of the last 2 rows and first 2 columns
np.var(dataset[-2:, :2])
```

前面代码的输出如下:

![Figure 1.30: Variance of a small subset of the dataset](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_30.jpg)

###### 图 1.30:数据集一小部分的方差
  1. The values of the variance might seem a little bit strange at first.
你可以随时回到*离差测量*主题来回顾你到目前为止所学的内容。

记住,方差不是标准差:

```py
# calculate the standard deviation for the dataset
np.std(dataset)
```

前面代码的输出如下:

![Figure 1.31: Standard deviation of the complete dataset](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_31.jpg)
图 1.31:完整数据集的标准差

恭喜你!您已经使用 NumPy 完成了第一项活动。在接下来的活动中,这些知识将得到进一步巩固。

活动 2:索引、切片、拆分和迭代

解决方案:

让我们使用 NumPy 的特性来索引、切片、分割和迭代数组。

索引

  1. 导入必要的库:

    # importing the necessary dependencies
    import numpy as np
    
  2. 使用 NumPy 加载normal_distribution.csv数据集。通过查看数组来确保一切正常,就像在前面的活动中一样:

    # loading the Dataset
    dataset = np.genfromtxt('./data/normal_distribution.csv', delimiter=',') 
    
  3. First, we want to use simple indexing for the second row, as we did in our first exercise. For a clearer understanding, all the elements are saved to a variable:

    # indexing the second row of the dataset (second row)
    second_row = dataset[1]
    np.mean(second_row)
    

    前面代码的输出如下:

    Figure 1.32: A screenshot of the mean of the whole second row

    图 1.32:整个第二行平均值的截图
  4. Now, we need to reverse index the last row and calculate the mean of that row. Always remember that providing a negative number as the index value will index the list from the end:

    # indexing the last element of the dataset (last row)
    last_row = dataset[-1]
    np.mean(last_row)
    

    前面代码的输出如下:

    图 1.33:计算最后一行的平均值
  5. Two-dimensional data can be accessed the same as with a Python List by using [0][0], where the first pair of brackets accesses the row and the second one accesses the column.

    但是,我们也可以使用逗号分隔符号,如[0, 0]:

    # indexing the first value of the second row (1st row, 1st value)
    first_val_first_row = dataset[0][0]
    np.mean(first_val_first_row)
    

    前面代码的输出如下:

    Figure 1.34: Calculating the mean of a single value won’t throw an error

    图 1.34:计算单个值的平均值不会出错
  6. The last value of the second last row can easily be accessed with reverse indexing. Remember that -1 means the last element:

    # indexing the last value of the second to last row (we want to use the combined access syntax here) 
    last_val_second_last_row = dataset[-2, -1]
    np.mean(last_val_second_last_row)
    

    前面代码的输出如下:

    Figure 1.35: Use of comma-separated notation

    图 1.35:逗号分隔符号的使用

    切片

  7. To create a 2x2 matrix that starts at the second row and second column, we use [1:3, 1:3]:

    # slicing an intersection of 4 elements (2x2) of the first two rows and first two columns
    subsection_2x2 = dataset[1:3, 1:3]
    np.mean(subsection_2x2)
    

    前面代码的输出如下:

    Figure 1.36: A screenshot of the mean of the 2x2 subset

    图 1.36:2 x2 子集平均值的屏幕截图
  8. Introducing the second column into the indexing allows us to add another layer of complexity. The third value allows us to only select certain values like every other element by providing the value of 2. This means it skips the values between and only takes each second element from the used list. In this task, we want to have every other element, so we provide an indexing of ::2, which, as we already discussed, will take every other element of the whole list:

    # selecting every second element of the fifth row 
    every_other_elem = dataset[6, ::2]
    np.mean(every_other_elem)
    

    前面代码的输出如下:

    图 1.37:选择第七行的每隔一个元素
  9. Negative numbers can also be used to reverse the elements in a slice:

    # reversing the entry order, selecting the first two rows in reversed order
    reversed_last_row = dataset[-1, ::-1]
    np.mean(reversed_last_row)
    

    前面代码的输出如下:

图 1.38:元素顺序相反的最后一行切片

分裂

  1. 水平分割我们的数据可以用hsplit方法完成。请注意,如果数据集不能用给定的切片数分割,它将抛出一个错误:

    # splitting up our dataset horizontally on indices one third and two thirds
    hor_splits = np.hsplit(dataset,(3)) 
    
  2. 我们现在需要将前三分之一垂直分成两个相等的部分。还有一种vsplit方法正是这么做的;它的工作原理与hsplit :

    # splitting up our dataset vertically on index 2
    ver_splits = np.vsplit(hor_splits[0],(2)) 
    

    相同

  3. When comparing the shapes, we can see that the subset has the required half of rows and the third half of columns:

    # requested subsection of our dataset which has only half the amount of rows and only a third of the columns
    print("Dataset", dataset.shape)
    print("Subset", ver_splits[0].shape)
    

    前面代码的输出如下:

    Figure 1.39: Comparing the shapes of the original dataset and subset

    图 1.39:比较原始数据集和子集的形状

    迭代

  4. Looking at the given piece of code, we can see that the index is simply incremented with each element.

    这仅适用于一维数据。如果我们想索引多维数据,这是行不通的:

    # iterating over whole dataset (each value in each row)
    curr_index = 0
    for x in np.nditer(dataset):
        print(x, curr_index)
        curr_index += 1 
    

    前面代码的输出如下:

    Figure 1.40: Iterating the entire dataset

    图 1.40:迭代整个数据集
  5. ndenumerate is the right method to use for this task. In addition to the value, it returns the index. This works with multi-dimensional data, too:

    # iterating over whole dataset with indices matching the position in the dataset
    for index, value in np.ndenumerate(dataset):
        print(index, value) 
    

    前面代码的输出如下:

    Figure 1.41: Enumerating the dataset with multi-dimensional data

图 1.41:用多维数据枚举数据集

恭喜你!我们已经介绍了 NumPy 的大多数基本数据争论方法。在下一个活动中,我们将了解更高级的功能,这些功能将为您提供更好地了解数据的工具。

活动 3:过滤、排序、组合和整形

解决方案:

让我们使用 NumPy 的过滤功能对数据进行排序、堆叠、组合和整形:

  1. 导入必要的库:

    # importing the necessary dependencies
    import numpy as np
    
  2. Load the normal_distribution.csv dataset using NumPy. Make sure that everything works by having a look at the ndarray, like in the previous activity:

    # loading the Dataset
    dataset = np.genfromtxt('./data/normal_distribution.csv', delimiter=',')
    

    过滤

  3. 获取大于 105 的值可以通过在括号中提供条件来完成:

    # values that are greater than 105
    vals_greater_five = dataset[dataset > 105]
    
  4. 要使用更复杂的条件,我们可能要使用 NumPy 的extract方法。但是,我们也可以使用括号符号进行相同的检查:

    # values that are between 90 and 95
    vals_between_90_95 = np.extract((dataset > 90) & (dataset < 95), dataset)
    
  5. The where method of numpy allows us to only get indices (rows, cols) for each of the matching values. In this task, we want to print them out nicely. We can combine rows with the respective cols using List comprehension. In this example, we have simply added the column to the respective row:

    # indices of values that have a delta of less than 1 to 100
    rows, cols = np.where(abs(dataset - 100) < 1)
    one_away_indices = [[rows[index], cols[index]] for (index, _) in np.ndenumerate(rows)]
    

    注意

    列表理解是 Python 映射数据的方式。它们是创建新列表的便捷符号,对旧列表的每个元素都应用了一些操作。

    例如,如果我们想将这个列表中每个元素的值增加一倍,list = [1, 2, 3, 4, 5],我们将使用类似如下的列表理解:doubled_list=[x*x for x in list]。这将为我们提供以下列表:[1, 4, 9, 16, 25]。为了更好地理解列表理解,请访问https://docs . python . org/3/tutorial/data structures . html #列表理解

    排序

  6. 对数据集中的每一行进行排序都是通过使用sort方法进行的。如笔记本中所述,这将始终采用最后一个轴,在这种情况下是每行排序的正确轴:

    # values sorted for each row
    row_sorted = np.sort(dataset)
    
  7. 对于多维数据,我们可以使用axis参数来定义应该对哪个数据集进行排序。0,在本例中,是指基于列的排序:

    # values sorted for each column
    col_sorted = np.sort(dataset, axis=0)
    
  8. If we want to keep the order of our dataset and only want to know which indices the values in a sorted dataset would have, we can use argsort. In combination with fancy indexing, we can get access to sorted elements easily:

    # indices of positions for each row
    index_sorted = np.argsort(dataset)
    

    组合

  9. Use combining features to add the second half of the first column back together, add the second column to our combined dataset, and add the third column to our combined dataset.

    # split up dataset from activity03
    thirds = np.hsplit(dataset, (3))
    halfed_first = np.vsplit(thirds[0], (2))
    # this is the part we've sent the client in activity03
    halfed_first[0]
    

    前面代码的输出如下:

    Figure 1.42: Splitting the dataset

    图 1.42:分割数据集
  10. 根据我们想要组合数据的方式,我们必须使用vstackhstack。两者都将数据集列表堆叠在一起:

```py
# adding the second half of the first column to the data
first_col = np.vstack([halfed_first[0], halfed_first[1]])
```
  1. 在 vstacking 分割数据集的后半部分后,我们再次将初始数据集的三分之一堆叠在一起。我们现在想将剩下的另外两个数据集添加到我们的first_col数据集。我们可以通过使用hstack方法来做到这一点,该方法将我们已经组合的first_col与三个分割数据集中的第二个:
```py
# adding the second column to our combined dataset
first_second_col = np.hstack([first_col, thirds[1]])
```

相结合
  1. To reassemble our initial dataset, one third is still missing. We can hstack the last one-third column onto our dataset, which is the same thing we did with our second-third column in the previous step:
```py
# adding the third column to our combined dataset
full_data = np.hstack([first_second_col, thirds[2]])
```

**重塑**
  1. 第一个子任务是将数据集重塑为单个列表。这是使用reshape方法完成的:
```py
# reshaping to a list of values
single_list = np.reshape(dataset, (1, -1))
```
  1. 如果我们为我们不知道的维度提供-1,NumPy 会自己计算出这个维度:
```py
# reshaping to a matrix with two columns
two_col_dataset = dataset.reshape(-1, 2)
```

活动 4:用熊猫计算给定数字的平均值、中位数和方差

解决方案:

让我们利用熊猫的特征,如平均值、中位数和方差,对我们的数据进行一些计算:

  1. 导入必要的库:

    # importing the necessary dependencies
    import pandas as pd
    
  2. 导入熊猫后,我们可以使用read_csv方法加载前面提到的数据集。我们希望使用包含国家名称的第一列作为我们的索引。我们将使用index_col参数。整行应该是这样的:

    # loading the Dataset
    dataset = pd.read_csv('./data/world_population.csv', index_col=0)
    
  3. First, we want to print a subset of the dataset with the first two rows. We can, again, use the Python List syntax to create a subset of the DataFrame of the first two rows:

    # looking at the first two rows of the dataset
    dataset[0:2]
    

    前面代码的输出如下:

    Figure 1.43: The first two rows, printed

    图 1.43:打印的前两行
  4. Once we know that our dataset has been successfully loaded, we can start solving the given tasks.

    第三行可以通过索引dataset.iloc[[2]]来访问。要获得国家的平均值而不是年度列,我们需要通过axis参数:

    # calculate the mean of the third row
    dataset.iloc[[2]].mean(axis=1)
    

    前面代码的输出如下:

    Figure 1.44: Calculating the mean of the third row

    图 1.44:计算第三行的平均值
  5. The last element of a DataFrame can, just like with NumPy ndarrays and Python Lists, be indexed using -1 as the index. So, dataset.iloc[[-1]] will give us the last row:

    # calculate the mean of the last row
    dataset.iloc[[-1]].mean(axis=1)
    

    前面代码的输出如下:

    Figure 1.45: Calculating the mean of the last row

    图 1.45:计算最后一行的平均值
  6. Besides using iloc to access rows based on their index, we can also use loc, which works based on the index column. This can be defined by using index_col=0 in the read_csv call:

    # calculate the mean of the country Germany
    dataset.loc[["Germany"]].mean(axis=1)
    

    前面代码的输出如下:

    图 1.46:对一个国家进行索引并计算德国的平均值
  7. Moving to the next set of tasks, which cover the usage of the median, we will see that the API is consistent between the different methods. This means that we have to keep providing axis=1 to our method calls to make sure that we are aggregating for each country:

    # calculate the median of the last row
    dataset.iloc[[-1]].median(axis=1)
    

    前面代码的输出如下:

    Figure 1.47: Usage of the median method on the last row

    图 1.47:最后一行中值法的用法
  8. Slicing rows in pandas is similar to doing so in NumPy. We can use reverse indexing to get the last three columns with dataset[-3:]:

    # calculate the median of the last 3 rows
    dataset[-3:].median(axis=1)
    

    前面代码的输出如下:

    Figure 1.48: Median of the last three columns

    图 1.48:最后三列的中位数
  9. When handling larger datasets, the order in which methods are executed matters. Think about what head(10) does for a moment. It simply takes your dataset and returns the first 10 rows in it, cutting down your input to the mean() method drastically.

    当使用更多内存密集型计算时,这肯定会产生影响,因此请注意顺序:

    # calculate the median of the first 10 countries
    dataset.head(10).median(axis=1)
    

    前面代码的输出如下:

    Figure 1.49: Usage of axis to calculate the median of the first 10 rows

    图 1.49:使用轴计算前 10 行的中间值
  10. The last method we'll cover here is the variance. Again, pandas provides us with a consistent API, which makes its usage easy. Since we only want to display the last five columns, we will make use of the tail method:

```py
# calculate the variance of the last 5 columns
dataset.var().tail()
```

前面代码的输出如下:

![Figure 1.50: Variance among the last five columns ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_50.jpg)

###### 图 1.50:最后五列之间的差异
  1. As mentioned in the introduction to pandas, it's interoperable with several features of NumPy.
这里有一个如何在熊猫数据框中使用 NumPy`mean`方法的例子。在某些情况下,NumPy 有更好的功能,但 pandas 及其数据帧有更好的格式:

```py
# NumPy pandas interoperability
import numpy as np
print("pandas", dataset["2015"].mean())
print("numpy", np.mean(dataset["2015"]))
```

前面代码的输出如下:

![Figure 1.51: Using NumPy’s mean method with a pandas DataFrame](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_51.jpg)
图 1.51:对熊猫数据帧使用 NumPy 的平均方法

恭喜你!您已经完成了与熊猫的第一次活动,这向您展示了与 NumPy 和熊猫合作时的一些相似之处和不同之处。在接下来的活动中,这些知识将得到巩固。你还将被介绍熊猫更复杂的特征和方法。

活动 5:使用熊猫进行索引、切片和迭代

解决方案:

让我们使用索引、切片和迭代操作来显示 1970 年、1990 年和 2010 年德国、新加坡、美国和印度的人口密度。

索引

  1. 导入必要的库:

    # importing the necessary dependencies
    import pandas as pd
    
  2. 导入熊猫后,我们可以使用read_csv方法加载上述数据集。我们希望使用包含国家名称的第一列作为我们的索引。我们将使用index_col参数。完整的一行应该是这样的:

    # loading the dataset
    dataset = pd.read_csv('./data/world_population.csv', index_col=0)
    
  3. To index the row with the index_col "United States", we need to use the loc method:

    # indexing the USA row
    dataset.loc[["United States"]].head()
    

    前面代码的输出如下:

    Figure 1.52: Indexing United States with the loc method

    图 1.52:用 loc 方法索引美国
  4. Reverse indexing can also be used with pandas. To index the second to last row, we need to use the iloc method, which accepts int-type data, meaning an index:

    # indexing the last second to last row by index
    dataset.iloc[[-2]]
    

    前面代码的输出如下:

    Figure 1.53: Indexing the second to last row

    图 1.53:索引第二行到最后一行
  5. Columns are indexed using their header. This is the first line of the CSV file. To get the column with the header 2000, we can use normal indexing. Remember, the head() method simply returns the first five rows:

    # indexing the column of 2000 as a Series
    dataset["2000"].head()
    

    前面代码的输出如下:

    Figure 1.54: Indexing all 2,000 columns

    图 1.54:索引所有 2000 列
  6. Since the double brackets notation again returns a DataFrame, we can chain method calls to get distinct elements. To get the population density of India in 2000, we first want to get the data for the year 2000 and only then select India using the loc() method:

    # indexing the population density of India in 2000 (Dataframe)
    dataset[["2000"]].loc[["India"]]
    

    前面代码的输出如下:

    Figure 1.55: Getting the population density of India in 2000

    图 1.55:2000 年印度的人口密度
  7. If we want to only retrieve a Series object, we have to replace the double brackets with single ones. This will give us the distinct value instead of the new DataFrame:

    # indexing the population density of India in 2000 (Series)
    dataset["2000"].loc["India"]
    

    前面代码的输出如下:

    Figure 1.56: India’s population density in the year 2000

    图 1.56:2000 年印度的人口密度

    切片

  8. To create a slice with the rows 2 to 5, we have to use the iloc() method again. We can simply provide the same slicing syntax as NumPy:

    # slicing countries of rows 2 to 5
    dataset.iloc[1:5]
    

    前面代码的输出如下:

    Figure 1.57: The countries in rows 2 to 5

    图 1.57:第 2 行至第 5 行中的国家
  9. Using the loc() method, we can also access dedicated rows by their index_col (which was defined in the read_csv call). To get several rows in a new DataFrame, we can use the nested brackets to provide a list of elements:

    # slicing rows Germany, Singapore, United States, and India 
    dataset.loc[["Germany", "Singapore", "United States", "India"]]
    

    前面代码的输出如下:

    Figure 1.58: Slicing countries Germany, Singapore, United States, and India

    图 1.58:切片国家德国、新加坡、美国和印度
  10. Since the double bracket queries return new DataFrames, we can chain methods and therefore access distinct subframes of our data:

```py
# slicing a subset of Germany, Singapore, United States, and India 
# for years 1970, 1990, 2010 <
country_list = ["Germany", "Singapore", "United States", "India"]
dataset.loc[country_list][["1970", "1990", "2010"]]
```

前面代码的输出如下:

![Figure 1.59: Slices of Germany, Singapore, United States, and India with their population density of years 1970, 1990, and 2010](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_59.jpg)

###### 图 1.59:1970 年、1990 年和 2010 年德国、新加坡、美国和印度的人口密度切片

**迭代**
  1. To iterate our dataset and print out the countries up until Angola, we can use the iterrows() method. The index will be the name of our row, and the row will hold all columns:
```py
# iterating over the first three countries (row by row)
for index, row in dataset.iterrows():
    # only printing the rows until Angola
    if index == 'Angola':
        break
    print(index, '\n', row[["Country Code", "1970", "1990", "2010"]], '\n')
```

前面代码的输出如下:

![Figure 1.60: Iterating all countries until Angola](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_60.jpg)
图 1.60:迭代所有国家,直到安哥拉

恭喜你!我们已经介绍了大多数使用熊猫的基本数据争论方法。在下一个活动中,我们将了解更高级的功能,如过滤、排序和整形,为下一章做准备。

活动 6:过滤、排序和整形

解决方案:

让我们用熊猫来过滤、分类和重塑我们的数据。

过滤

  1. 导入必要的库:

    # importing the necessary dependencies
    import pandas as pd
    
  2. 导入熊猫后,我们可以使用read_csv方法加载前面提到的数据集。我们希望使用包含国家名称的第一列作为我们的索引。我们将使用index_col参数。完整的一行应该是这样的:

    # loading the dataset
    dataset = pd.read_csv('./data/world_population.csv', index_col=0)
    
  3. Instead of using the bracket syntax, we can also use the filter method to filter for specific items. Here, we also provide a list of elements that should be kept:

    # filtering columns 1961, 2000, and 2015
    dataset.filter(items=["1961", "2000", "2015"]).head()
    

    前面代码的输出如下:

    Figure 1.61: Filtering data for the years 1961, 2000, and 2015

    图 1.61:过滤 1961 年、2000 年和 2015 年的数据
  4. If we want to filter for specific values in a specific column, we can use conditions. To get all countries that had a higher population density than 500 in 2000, we simply pass this condition in brackets:

    # filtering countries that had a greater population density than 500 in 2000
    dataset[(dataset["2000"] > 500)][["2000"]]
    

    前面代码的输出如下:

    Figure 1.62: Filtering out values that are greater than 500 in the 2000 column

    图 1.62:过滤掉 2000 列中大于 500 的值
  5. One powerful parameter of the filter method is regex. This allows us to search for arbitrary columns or rows (depending on the index given) that match a certain regex. To get all the columns that start with 2, we can simply pass ^2, which means it starts with 2:

    # filtering for years 2000 and later
    dataset.filter(regex="^2", axis=1).head()
    

    前面代码的输出如下:

    Figure 1.63: Retrieving all columns starting with 2

    图 1.63:检索从 2 开始的所有列
  6. Using the axis parameter, we can decide on which dimension the filtering should happen. To filter the rows instead of the columns, we can pass axis=0. This will be helpful for situations like when we want to filter all the rows that start with A:

    # filtering countries that start with A
    dataset.filter(regex="^A", axis=0).head()
    

    前面代码的输出如下:

    Figure 1.64: Retrieving the rows that start with A

    图 1.64:检索以 A 开头的行
  7. If we want all the rows or columns that contain some specific value or character, we can use the like query. For example, if we want to have only countries that contain the word "land", such as Switzerland:

    # filtering countries that contain the word land
    dataset.filter(like="land", axis=0).head()
    

    前面代码的输出如下:

    Figure 1.65: Retrieving all countries containing the word “land”

    图 1.65:检索包含“土地”一词的所有国家

    排序

  8. Sorting in pandas can be done by using the sort_values or sort_index methods. If we want to get the countries with the lowest population density for a specific year, we can sort by this specific column:

    # values sorted by column 1961
    dataset.sort_values(by=["1961"])[["1961"]].head(10)
    

    前面代码的输出如下:

    Figure 1.66: Sorting by the values for the year 1961

    图 1.66:按 1961 年的值排序
  9. Just for comparison, we want to do the same sorting for 2015. This will give us some nice insights into our data. We can see that the order of the countries with the lowest population density changed a bit, but that the first three entries are unchanged:

    # values sorted by column 2015
    dataset.sort_values(by=["2015"])[["2015"]].head(10)
    

    前面代码的输出如下:

    Figure 1.67: Sorting based on the values of 2015

    图 1.67:基于 2015 年值的排序
  10. The default sorting order is ascending. This means that we have to provide a separate parameter if we want to sort in descending order, showing us the biggest values first:

```py
# values sorted by column 2015 in descending order
dataset.sort_values(by=["2015"], ascending=False)[["2015"]].head(10)
```

前面代码的输出如下:

![Figure 1.68: Sorting in descending order](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_68.jpg)

###### 图 1.68:按降序排序

**重塑**
  1. As we mentioned before, reshaping data with pandas can be a very complex task, so we'll only do an easier reshape. We want to get DataFrames where the columns are country codes and the only row is the year 2015. Since we only have one 2015 label, we need to duplicate it as many times as our dataset length. This will lead to every value receiving the 2015 row index:
```py
# reshaping to 2015 as row and country codes as columns
dataset_2015 = dataset[["Country Code", "2015"]]
dataset_2015.pivot(index=["2015"] * len(dataset_2015), columns="Country Code", values="2015")
```

前面代码的输出如下:

![Figure 1.69: Reshaping the dataset into a single row for the values of 2015](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_01_69.jpg)
图 1.69:对于 2015 年的值,将数据集重塑为单行

你现在已经完成了关于熊猫的主题,这一章到此结束。我们已经了解了帮助您争论和处理数据的基本工具。熊猫本身是一个令人难以置信的强大和大量使用的数据争论和理解工具。

第二章:关于绘图你需要知道的一切

活动 7:员工技能比较

解决方案:

  1. 条形图和雷达图非常适合比较多个组的多个变量。

  2. 建议回应:条形图对于比较不同员工的技能属性很有帮助,但是对于一个员工来说,不利于获得整体印象。雷达图非常适合比较员工和技能属性的价值。

  3. Suggested response:

    条形图:添加标题和标签,使用不同的颜色

    雷达图:为不同的员工增加一个标题和不同的颜色

活动 8:二十年来发生的道路事故

解决方案:

  1. 建议回应:我们看一下图 2.20,可以看到 2015 年 1、7 月份事故数量已经减少到 100 起。4 月和 10 月发生了大约 300 起事故。
  2. 建议回应:如果我们看一下每个月的趋势,也就是过去二十年的 1 月、4 月、7 月和 10 月,我们可以看到 1 月份发生的事故数量有减少的趋势。

设计实践:

  • 选择视觉有问题的人容易看到的颜色和对比度,这样你的绘图就更具包容性。

活动 9:智能手机销售单位

解决方案:

  1. 建议回应:如果对比一下三四季度各个厂商的表现,我们得出苹果表现异常出色的结论。与其他制造商相比,2016 年和 2017 年第三季度至第四季度,他们的销售单位都以更高的速度增长。
  2. 建议回应:如果我们看一下各个厂商的销量走势,可以看到 2017 年第三季度之后,除了小米以外的所有公司的销量都出现了不一致的情况。如果看小米的表现,2017 年一季度之后一直有上升趋势。

活动 10:不同时间间隔的列车频率

解决方案:

  1. 建议回应:如果我们关注前面的直方图,可以清楚的识别出大部分列车在下午 6 点和 8 点到达。
  2. 建议响应:如果下午 4-6 点之间的列车数量增加 50 辆,柱状图将如下所示:

Figure 2.45: Frequency of trains increased by 50 between 4-6 pm

图 2.45:下午 4-6 点之间列车班次增加 50

活动 11:确定理想的可视化

解决方案:

首次可视化 : 建议响应:

  1. 提出的可视化有多个缺点:首先,饼图应该显示整体关系的一部分,这不是这个任务的情况,因为我们只考虑前 30 个优酷。其次,30 个值太多,无法在饼图中可视化。第三,切片不是按照大小排序的。此外,由于没有指定测量单位,因此很难量化切片。另一方面,在下面的横条图中,更容易看出每个 YouTube 频道的订户数量(百万):

Figure 2.46: Horizontal bar chart showing Top 30 YouTubers

图 2.46:显示前 30 名优酷的水平条形图

第二次可视化 : 建议响应:

  1. 这也是使用错误图表类型的一个例子。折线图用于比较没有任何时间关系的不同类别。此外,图例和标签等信息缺失。下图显示了应该如何使用比较条形图表示数据:

Figure 2.47: Comparative bar chart displaying casino data for two days

图 2.47:显示两天赌场数据的比较条形图

第三章:深入 Matplotlib

活动 12:使用线图可视化股票趋势

解决方案:

让我们用一个线形图来可视化股票趋势:

  1. Open the activity12_solution.ipynb Jupyter Notebook from the Lesson03 folder to implement this activity.

    导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import statements
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    %matplotlib inline
    
  3. 使用熊猫读取数据文件夹中的数据。read_csv()功能将. csv 文件读入数据框 :

    # load datasets
    google = pd.read_csv('./data/GOOGL_data.csv')
    facebook = pd.read_csv('./data/FB_data.csv')
    apple = pd.read_csv('./data/AAPL_data.csv')
    amazon = pd.read_csv('./data/AMZN_data.csv')
    microsoft = pd.read_csv('./data/MSFT_data.csv')
    
  4. 使用 Matplotlib 创建一个折线图,可视化所有五家公司过去五年的收盘价(整个数据序列)。添加标签、标题和图例,使可视化不言自明。使用plt.grid()功能为您的绘图添加网格:

    # Create figure
    plt.figure(figsize=(16, 8), dpi=300)
    # Plot data
    plt.plot('date', 'close', data=google, label='Google')
    plt.plot('date', 'close', data=facebook, label='Facebook')
    plt.plot('date', 'close', data=apple, label='Apple')
    plt.plot('date', 'close', data=amazon, label='Amazon')
    plt.plot('date', 'close', data=microsoft, label='Microsoft')
    # Specify ticks for x and y axis
    plt.xticks(np.arange(0, 1260, 40), rotation=70)
    plt.yticks(np.arange(0, 1450, 100))
    # Add title and label for y-axis
    plt.title('Stock trend', fontsize=16)
    plt.ylabel('Closing price in $', fontsize=14)
    # Add grid
    plt.grid()
    # Add legend
    plt.legend()
    # Show plot
    plt.show()
    

活动 13:创建用于电影比较的条形图

解决方案:

让我们创建一个条形图来比较不同电影的评分:

  1. Open the activity13_solution.ipynb Jupyter Notebook from the Lesson03 folder to implement this activity.

    导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图。;

    # Import statements
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  3. 用熊猫读取数据文件夹中的数据:

    # Load dataset
    movie_scores = pd.read_csv('./data/movie_scores.csv')
    
  4. 使用 Matplotlib 创建一个视觉上吸引人的条形图,比较所有五部电影的两个分数。使用电影标题作为 x 轴的标签。对 y 轴刻度使用 20 的间隔百分比,对 5 的间隔使用次要刻度。给绘图添加一个图例和合适的标题:

    # Create figure
    plt.figure(figsize=(10, 5), dpi=300)
    # Create bar plot
    pos = np.arange(len(movie_scores['MovieTitle']))
    width = 0.3
    plt.bar(pos - width / 2, movie_scores['Tomatometer'], width, label='Tomatometer')
    plt.bar(pos + width / 2, movie_scores['AudienceScore'], width, label='Audience Score')
    # Specify ticks
    plt.xticks(pos, rotation=10)
    plt.yticks(np.arange(0, 101, 20))
    # Get current Axes for setting tick labels and horizontal grid
    ax = plt.gca()
    # Set tick labels
    ax.set_xticklabels(movie_scores['MovieTitle'])
    ax.set_yticklabels(['0%', '20%', '40%', '60%', '80%', '100%'])
    # Add minor ticks for y-axis in the interval of 5
    ax.set_yticks(np.arange(0, 100, 5), minor=True)
    # Add major horizontal grid with solid lines
    ax.yaxis.grid(which='major')
    # Add minor horizontal grid with dashed lines
    ax.yaxis.grid(which='minor', linestyle='--')
    # Add title
    plt.title('Movie comparison')
    # Add legend
    plt.legend()
    # Show plot
    plt.show()
    
  5. 有些功能要求您明确指定。要获得对当前轴的引用,请使用ax = plt.gca()。对于 x 轴和 y 轴,可以分别使用plt.xticks([xticks])plt.yticks([yticks])指定刻度。Axes.set_xticklabels([labels])Axes.set_yticklabels([labels])可用于设置刻度标签。要添加次要的 y 记号,请使用Axes.set_yticks([ticks], minor=True)。要为主要刻度添加水平网格,请使用Axes.yaxis.grid(which='major'),要为次要刻度添加虚线水平网格,请使用Axes.yaxis.grid(which='minor', linestyle='--')

活动 14:创建堆叠条形图以可视化餐厅表现

解决方案:

让我们创建一个堆叠条形图来可视化餐厅的表现:

  1. Open the activity14_solution.ipynb Jupyter Notebook from the Lesson03 folder to implement this activity.

    导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import statements
    import pandas as sb
    import numpy as np
    import matplotlib.pyplot as plt
    import seaborn as sns
    %matplotlib inline
    
  3. 加载数据集:

    # Load dataset
    bills = sns.load_dataset('tips')
    
  4. Use the given dataset and create a matrix where the elements contain the sum of the total bills for each day and smokers/non-smokers:

    days = ['Thur', 'Fri', 'Sat', 'Sun']
    days_range = np.arange(len(days))
    smoker = ['Yes', 'No']
    bills_by_days = [bills[bills['day'] == day] for day in days]
    bills_by_days_smoker = [[bills_by_days[day][bills_by_days[day]['smoker'] == s] for s in smoker] for day in days_range]
    total_by_days_smoker = [[bills_by_days_smoker[day][s]['total_bill'].sum() for s in range(len(smoker))] for day in days_range]
    totals = np.asarray(total_by_days_smoker)
    

    这里,asarray()函数用于将任何给定的输入转换成数组。

  5. 创建一个堆积条形图,将吸烟者和非吸烟者每天的总账单堆积在一起。添加图例、标签和标题:

    # Create figure
    plt.figure(figsize=(10, 5), dpi=300)
    # Create stacked bar plot
    plt.bar(days_range, totals[:, 0], label='Smoker')
    plt.bar(days_range, totals[:, 1], bottom=totals[:, 0], label='Non-smoker')
    # Add legend
    plt.legend()
    # Add labels and title
    plt.xticks(days_range)
    ax = plt.gca()
    ax.set_xticklabels(days)
    ax.yaxis.grid()
    plt.ylabel('Daily total sales in $')
    plt.title('Restaurant performance')
    # Show plot
    plt.show()
    

活动 15:使用堆叠面积图比较智能手机销量

解决方案:

让我们使用堆叠面积图来比较智能手机制造商的销售单位:

  1. Open the activity15_solution.ipynb Jupyter Notebook from the Lesson03 folder to implement this activity.

    导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import statements
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  3. 用熊猫读取数据文件夹中的数据:

    # Load dataset
    sales = pd.read_csv('./data/smartphone_sales.csv')
    
  4. 创建一个视觉上吸引人的堆叠面积图。添加图例、标签和标题:

    # Create figure
    plt.figure(figsize=(10, 6), dpi=300)
    # Create stacked area chart
    labels = sales.columns[1:]
    plt.stackplot('Quarter', 'Apple', 'Samsung', 'Huawei', 'Xiaomi', 'OPPO', data=sales, labels=labels)
    # Add legend
    plt.legend()
    # Add labels and title
    plt.xlabel('Quarters')
    plt.ylabel('Sales units in thousands')
    plt.title('Smartphone sales units')
    # Show plot
    plt.show()
    

活动 16:使用直方图和箱线图来可视化智商

解决方案:

让我们使用直方图和箱线图来可视化不同群体的智商:

  1. Open the activity16_solution.ipynb Jupyter Notebook from the Lesson03 folder to implement this activity.

    导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import statements
    import numpy as np
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  3. 使用以下代码生成智商分数样本:

    # IQ samples
    iq_scores = [126,  89,  90, 101, 102,  74,  93, 101,  66, 120, 108,  97,  98, 105, 119,  92, 113,  81, 104, 108,  83, 102, 105, 111, 102, 107, 103,  89,  89, 110,  71, 110, 120,  85, 111,  83, 122, 120, 102, 84, 118, 100, 100, 114,  81, 109,  69,  97,  95, 106, 116, 109, 114,  98,  90,  92,  98,  91,  81,  85,  86, 102,  93, 112,  76, 89, 110,  75, 100,  90,  96,  94, 107, 108,  95,  96,  96, 114, 93,  95, 117, 141, 115,  95,  86, 100, 121, 103,  66,  99,  96, 111, 110, 105, 110, 91, 112, 102, 112,  75]
    
  4. 为给定的智商分数绘制一个包含 10 个面元的直方图。智商分数呈正态分布,平均值为 100,标准差为 15。将平均值可视化为垂直的红色实线,用垂直虚线表示标准偏差。添加标签和标题:

    # Create figure
    plt.figure(figsize=(6, 4), dpi=150)
    # Create histogram
    plt.hist(iq_scores, bins=10)
    plt.axvline(x=100, color='r')
    plt.axvline(x=115, color='r', linestyle= '--')
    plt.axvline(x=85, color='r', linestyle= '--')
    # Add labels and title
    plt.xlabel('IQ score')
    plt.ylabel('Frequency')
    plt.title('IQ scores for a test group of a hundred adults')
    # Show plot
    plt.show()
    
  5. 创建一个方框图来可视化相同的智商分数。添加标签和标题:

    # Create figure
    plt.figure(figsize=(6, 4), dpi=150)
    # Create histogram
    plt.boxplot(iq_scores)
    # Add labels and title
    ax = plt.gca()
    ax.set_xticklabels(['Test group'])
    plt.ylabel('IQ score')
    plt.title('IQ scores for a test group of a hundred adults')
    # Show plot
    plt.show()
    
  6. 以下是不同测试组的智商分数:

    group_a = [118, 103, 125, 107, 111,  96, 104,  97,  96, 114,  96,  75, 114,
           107,  87, 117, 117, 114, 117, 112, 107, 133,  94,  91, 118, 110,
           117,  86, 143,  83, 106,  86,  98, 126, 109,  91, 112, 120, 108,
           111, 107,  98,  89, 113, 117,  81, 113, 112,  84, 115,  96,  93,
           128, 115, 138, 121,  87, 112, 110,  79, 100,  84, 115,  93, 108,
           130, 107, 106, 106, 101, 117,  93,  94, 103, 112,  98, 103,  70,
           139,  94, 110, 105, 122,  94,  94, 105, 129, 110, 112,  97, 109,
           121, 106, 118, 131,  88, 122, 125,  93,  78]
    group_b = [126,  89,  90, 101, 102,  74,  93, 101,  66, 120, 108,  97,  98,
                105, 119,  92, 113,  81, 104, 108,  83, 102, 105, 111, 102, 107,
                103,  89,  89, 110,  71, 110, 120,  85, 111,  83, 122, 120, 102,
                84, 118, 100, 100, 114,  81, 109,  69,  97,  95, 106, 116, 109,
                114,  98,  90,  92,  98,  91,  81,  85,  86, 102,  93, 112,  76,
                89, 110,  75, 100,  90,  96,  94, 107, 108,  95,  96,  96, 114,
                93,  95, 117, 141, 115,  95,  86, 100, 121, 103,  66,  99,  96,
                111, 110, 105, 110,  91, 112, 102, 112,  75]
    group_c = [108,  89, 114, 116, 126, 104, 113,  96,  69, 121, 109, 102, 107,
           122, 104, 107, 108, 137, 107, 116,  98, 132, 108, 114,  82,  93,
            89,  90,  86,  91,  99,  98,  83,  93, 114,  96,  95, 113, 103,
            81, 107,  85, 116,  85, 107, 125, 126, 123, 122, 124, 115, 114,
            93,  93, 114, 107, 107,  84, 131,  91, 108, 127, 112, 106, 115,
            82,  90, 117, 108, 115, 113, 108, 104, 103,  90, 110, 114,  92,
           101,  72, 109,  94, 122,  90, 102,  86, 119, 103, 110,  96,  90,
           110,  96,  69,  85, 102,  69,  96, 101,  90]
    group_d = [ 93,  99,  91, 110,  80, 113, 111, 115,  98,  74,  96,  80,  83,
           102,  60,  91,  82,  90,  97, 101,  89,  89, 117,  91, 104, 104,
           102, 128, 106, 111,  79,  92,  97, 101, 106, 110,  93,  93, 106,
           108,  85,  83, 108,  94,  79,  87, 113, 112, 111, 111,  79, 116,
           104,  84, 116, 111, 103, 103, 112,  68,  54,  80,  86, 119,  81,
            84,  91,  96, 116, 125,  99,  58, 102,  77,  98, 100,  90, 106,
           109, 114, 102, 102, 112, 103,  98,  96,  85,  97, 110, 131,  92,
            79, 115, 122,  95, 105,  74,  85,  85,  95]
    
  7. 为不同测试组的每个智商分数创建一个方框图。添加标签和标题:

    # Create figure
    plt.figure(figsize=(6, 4), dpi=150)
    # Create histogram
    plt.boxplot([group_a, group_b, group_c, group_d])
    # Add labels and title
    ax = plt.gca()
    ax.set_xticklabels(['Group A', 'Group B', 'Group C', 'Group D'])
    plt.ylabel('IQ score')
    plt.title('IQ scores for different test groups')
    # Show plot
    plt.show()
    

活动 17:使用散点图来可视化各种动物之间的相关性

解决方案:

让我们借助散点图来可视化各种动物之间的相关性:

  1. Open the activity17_solution.ipynb Jupyter Notebook from the Lesson03 folder to implement this activity.

    导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import statements
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  3. 用熊猫读取数据文件夹中的数据:

    # Load dataset 
    data = pd.read_csv('./data/anage_data.csv')
    
  4. 给定的数据集不完整。过滤数据,这样你最终得到的样本就包含了一个体重和最长寿命。根据动物种类对数据进行分类。这里,isfinite()函数检查给定元素的有限性:

    # Preprocessing
    longevity = 'Maximum longevity (yrs)'
    mass = 'Body mass (g)'
    data = data[np.isfinite(data[longevity]) & np.isfinite(data[mass])]
    # Sort according to class
    amphibia = data[data['Class'] == 'Amphibia']
    aves = data[data['Class'] == 'Aves']
    mammalia = data[data['Class'] == 'Mammalia']
    reptilia = data[data['Class'] == 'Reptilia']
    
  5. 创建散点图,可视化体重和最大寿命之间的相关性。根据数据样本的类别,使用不同的颜色对数据样本进行分组。添加图例、标签和标题。x 轴和 y 轴都使用对数刻度:

    # Create figure
    plt.figure(figsize=(10, 6), dpi=300)
    # Create scatter plot
    plt.scatter(amphibia[mass], amphibia[longevity], label='Amphibia')
    plt.scatter(aves[mass], aves[longevity], label='Aves')
    plt.scatter(mammalia[mass], mammalia[longevity], label='Mammalia')
    plt.scatter(reptilia[mass], reptilia[longevity], label='Reptilia')
    # Add legend
    plt.legend()
    # Log scale
    ax = plt.gca()
    ax.set_xscale('log')
    ax.set_yscale('log')
    # Add labels
    plt.xlabel('Body mass in grams')
    plt.ylabel('Maximum longevity in years')
    # Show plot
    plt.show()
    

活动 18:创建带有边缘直方图的散点图

解决方案:

  1. Open the activity18_solution.ipynb Jupyter Notebook from the Lesson03 folder to implement this activity.

    导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import statements
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    %matplotlib inline
    
  3. 用熊猫读取数据文件夹中的数据:

    # Load dataset
    data = pd.read_csv('./data/anage_data.csv')
    
  4. 给定的数据集不完整。过滤数据,这样你最终得到的样本就包含了一个体重和最长寿命。选择所有aves类且体重小于 20,000 的样本:

    # Preprocessing
    longevity = 'Maximum longevity (yrs)'
    mass = 'Body mass (g)'
    data = data[np.isfinite(data[longevity]) & np.isfinite(data[mass])]
    # Sort according to class
    aves = data[data['Class'] == 'Aves']
    aves = data[data[mass] < 20000]
    
  5. 创建具有受约束布局的图形。创建一个 4x4 大小的网格像素。创建大小为 3x3 的散点图和大小为 1x3 和 3x1 的边缘直方图。添加标签和图形标题:

    # Create figure
    fig = plt.figure(figsize=(8, 8), dpi=150, constrained_layout=True)
    # Create gridspec
    gs = fig.add_gridspec(4, 4)
    # Specify subplots
    histx_ax = fig.add_subplot(gs[0, :-1])
    histy_ax = fig.add_subplot(gs[1:, -1])
    scatter_ax = fig.add_subplot(gs[1:, :-1])
    # Create plots
    scatter_ax.scatter(aves[mass], aves[longevity])
    histx_ax.hist(aves[mass], bins=20, density=True)
    histx_ax.set_xticks([])
    histy_ax.hist(aves[longevity], bins=20, density=True, orientation='horizontal')
    histy_ax.set_yticks([])
    # Add labels and title
    plt.xlabel('Body mass in grams')
    plt.ylabel('Maximum longevity in years')
    fig.suptitle('Scatter plot with marginal histograms')
    # Show plot
    plt.show()
    

活动 19:在网格中绘制多个图像

解决方案:

  1. Open the activity19_solution.ipynb Jupyter Notebook from the Lesson03 folder to implement this activity.

    导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab.

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    # Import statements
    import os
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.image as mpimg
    %matplotlib inline
    
  3. 从数据文件夹加载所有四个图像:

    # Load images
    img_filenames = os.listdir('data')
    imgs = [mpimg.imread(os.path.join('data', img_filename)) for img_filename in img_filenames]
    
  4. 在 2x2 网格中可视化图像。移除轴并给每个图像一个标签:

    # Create subplot
    fig, axes = plt.subplots(2, 2)
    fig.figsize = (6, 6)
    fig.dpi = 150
    axes = axes.ravel()
    # Specify labels
    labels = ['coast', 'beach', 'building', 'city at night']
    # Plot images
    for i in range(len(imgs)):
        axes[i].imshow(imgs[i])
        axes[i].set_xticks([])
        axes[i].set_yticks([])
        axes[i].set_xlabel(labels[i])
    

第 4 章:使用 Seaborn 简化可视化

活动 20:使用箱线图比较不同测试组的智商得分

解决方案:

让我们使用 Seaborn 库来比较不同测试组的智商得分:

  1. Lesson04文件夹打开activity20_solution.ipynb Jupyter 笔记本,执行本活动。导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图。

    %matplotlib inline
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  3. 使用熊猫read_csv()功能读取数据文件夹中的数据:

    mydata = pd.read_csv("./data/scores.csv")
    
  4. 访问列中每个测试组的数据。使用tolist()方法将其转换为列表。一旦每个测试组的数据被转换成一个列表,将这个列表分配给每个测试组的变量:

    group_a = mydata[mydata.columns[0]].tolist()
    group_b = mydata[mydata.columns[1]].tolist()
    group_c = mydata[mydata.columns[2]].tolist()
    group_d = mydata[mydata.columns[3]].tolist()
    
  5. Print the variables of each group to check whether the data inside it is converted into a list. This can be done with the help of the print() function:

    print(group_a)
    

    A 组的数据值如下图所示:

    Figure 4.35: Values of Group A

    图 4.35:A 组的值
    print(group_b)
    

    乙组的数据值如下图所示:

    Figure 4.36: Values of Group B

    图 4.36:乙组的数值
    print(group_c)
    

    C 组的数据值如下图所示:

    Figure 4.37: Values of Group C

    图 4.37:C 组的值
    print(group_d)
    

    D 组的数据值如下图所示:

    Figure 4.38: Values of Group D

    图 4.38:D 组的值
  6. 一旦我们有了每个测试组的数据,我们就需要从这个给定的数据构建一个数据框架。这可以借助熊猫提供的pd.DataFrame()功能来完成。

    data = pd.DataFrame({'Groups': ['Group A'] * len(group_a) + ['Group B'] * len(group_b) + ['Group C'] * len(group_c) + ['Group D'] * len(group_d),
                         'IQ score': group_a + group_b + group_c + group_d})
    
  7. Now, since we have the DataFrame, we need to create a box plot using the boxplot() function that's provided by Seaborn. Within this function, we need to specify the titles for both the axes along with the DataFrame we are using. The title for the x-axis would be Groups and the title for the y-axis would be IQ score. As far as the DataFrame is concerned, we will pass data as a parameter. Here, data is the DataFrame that we obtained from the previous step.

    plt.figure(dpi=150)
    # Set style
    sns.set_style('whitegrid')
    # Create boxplot
    sns.boxplot('Groups', 'IQ score', data=data)
    # Despine
    sns.despine(left=True, right=True, top=True)
    # Add title
    plt.title('IQ scores for different test groups')
    # Show plot
    plt.show() 
    

    despine()功能有助于从图中移除顶部和右侧的刺。这里,我们还切除了左侧脊柱。使用title()功能,我们已经为我们的绘图设置了标题。show()功能有助于可视化绘图。

从图 4.8 中,我们可以得出结论,通过使用方框图,A 组的智商得分优于其他组。

活动 21:使用热图寻找航班乘客数据中的模式

解决方案:

让我们借助热图找出航班乘客数据中的模式:

  1. Lesson04文件夹打开activity21_solution.ipynb Jupyter 笔记本,执行本活动。导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    %matplotlib inline
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  3. 使用熊猫的read_csv()功能读取数据文件夹中的数据:

    mydata = pd.read_csv("./data/flight_details.csv")
    
  4. 现在,我们可以使用pivot()函数为我们的数据框提供有意义的行和列标签:

    data = mydata.pivot("Months", "Years", "Passengers")
    
  5. 使用 Seaborn 库的heatmap()功能来可视化这些数据。在这个函数中,我们传递数据帧和颜色映射等参数。由于我们从前面的代码中获得了数据,我们将把它作为heatmap()函数中的数据框传递。此外,我们将创建自己的颜色映射,并将其作为第二个参数传递给这个函数。

    sns.set()
    plt.figure(dpi=150)
    sns.heatmap(data, cmap=sns.light_palette("orange", as_cmap=True, reverse=True))
    plt.title("Flight Passengers from 2001 to 2012")
    plt.show()
    

从图 4.23 中,我们可以得出结论,2012 年 7 月和 8 月的航班乘客数量最高。

活动 22:重温电影对比

解决方案:

让我们使用 Seaborn library 提供的条形图来比较五部不同电影的电影评分:

  1. Lesson04文件夹打开activity22_solution.ipynb Jupyter 笔记本,执行本活动。导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    %matplotlib inline
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  3. 使用熊猫的read_csv()功能读取数据文件夹中的数据:

    mydata = pd.read_csv("./data/movie_scores.csv")
    
  4. 从这个给定的数据构造一个数据帧。这可以借助熊猫提供的pd.DataFrame()功能来完成。下面的代码让我们对此有了更好的了解:

    movie_scores = pd.DataFrame({"Movie Title": list(mydata["MovieTitle"]) * 2,
                    "Score": list(mydata["AudienceScore"]) + list(mydata["Tomatometer"]),
                    "Type": ["Audience Score"] * len(mydata["AudienceScore"]) + ["Tomatometer"] * len(mydata["Tomatometer"])})
    
  5. Make use of the barplot() function provided by Seaborn. Provide Movies and Scores as parameters so that their data is displayed on both axes. Provide Type as hue, on the basis of which the comparison needs to be made. The last parameter needs a DataFrame as input. Thus, we provide the movie_scores DataFrame, which we obtained from the previous step.

    下面的代码对此有更好的理解:

    sns.set()
    plt.figure(figsize=(10, 5), dpi=300)
    # Create bar plot
    ax = sns.barplot("Movie Title", "Score", hue="Type", data=movie_scores)
    plt.xticks(rotation=10)
    # Add title
    plt.title("Movies Scores comparison")
    plt.xlabel("Movies")
    plt.ylabel("Scores")
    # Show plot
    plt.show()
    

我们比较了 AudienceScore 和 Tomatometer 对 5 部不同电影的评分,并得出结论,电影《T0》和《火星人》的评分相匹配。

活动 23:使用小提琴图比较不同测试组的智商得分

解决方案:

让我们使用 Seaborn 库来比较不同测试组的智商得分:

  1. Lesson04文件夹打开activity23_solution.ipynb Jupyter 笔记本,执行本活动。导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    %matplotlib inline
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  3. 使用熊猫的read_csv()功能读取数据文件夹中的数据:

    mydata = pd.read_csv("./data/scores.csv")
    
  4. 访问列中每个测试组的数据。使用tolist()方法将其转换为列表。一旦每个测试组的数据被转换成一个列表,将这个列表分配给每个测试组的变量。

    group_a = mydata[mydata.columns[0]].tolist()
    group_b = mydata[mydata.columns[1]].tolist()
    group_c = mydata[mydata.columns[2]].tolist()
    group_d = mydata[mydata.columns[3]].tolist()
    
  5. Print the variables of each group to check whether the data inside it has been converted into a list. This can be done with the help of the print() function:

    print(group_a)
    

    Figure 4.39: Values of Group A

    图 4.39:A 组的值
    print(group_b)
    

    Figure 4.40: Values of Group B

    图 4.40:乙组的数值
    print(group_c)
    

    Figure 4.41: Values of Group C

    图 4.41:C 组的值
    print(group_d)
    

    Figure 4.42: Values of Group D

    图 4.42:D 组的值
  6. 一旦我们获得了每个测试组的数据,我们需要从这个给定的数据中构建一个数据框架。这可以通过熊猫提供的pd.DataFrame()功能来实现。

    data = pd.DataFrame({'Groups': ['Group A'] * len(group_a) + ['Group B'] * len(group_b) + ['Group C'] * len(group_c) + ['Group D'] * len(group_d),
                         'IQ score': group_a + group_b + group_c + group_d})
    
  7. Now, since we have the DataFrame, we need to create a violin plot using the violinplot() function that's provided by Seaborn. Within this function, we need to specify the titles for both the axes along with the DataFrame we are using. The title for the x-axis will be Groups and the title for the y-axis will be IQ score. As far as the DataFrame is concerned, we will pass data as a parameter. Here, data is the DataFrame that we obtained from the previous step.

    plt.figure(dpi=150)
    # Set style
    sns.set_style('whitegrid')
    # Create boxplot
    sns.violinplot('Groups', 'IQ score', data=data)
    # Despine
    sns.despine(left=True, right=True, top=True)
    # Add title
    plt.title('IQ scores for different test groups')
    # Show plot
    plt.show() 
    

    despine()功能有助于从图中移除顶部和右侧的刺。这里,我们还切除了左侧脊柱。使用title()功能,我们已经为我们的绘图设置了标题。show()功能有助于可视化绘图。

我们可以得出结论,A 组的智商得分比其他组好。

活动 24:排名前 30 的 YouTube 频道

解决方案:

让我们通过使用由 Seaborn 图书馆提供的FacetGrid()功能来可视化前 30 个 YouTube 频道的订户总数和总浏览量:

  1. Lesson04文件夹打开activity24_solution.ipynb Jupyter 笔记本,执行本活动。导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    %matplotlib inline
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  3. 使用熊猫的read_csv()功能读取数据文件夹中的数据:

    mydata = pd.read_csv("./data/youtube.csv")
    
  4. 访问列中每个测试组的数据。使用tolist()方法将其转换为列表。一旦每个测试组的数据被转换成一个列表,将这个列表分配给每个测试组的变量。

    channels = mydata[mydata.columns[0]].tolist()
    subs = mydata[mydata.columns[1]].tolist()
    views = mydata[mydata.columns[2]].tolist()
    
  5. Print the variables of each group to check whether the data inside it has been converted into a list. This can be done with the help of the print() function:

    print(channels)
    

    Figure 4.43: List of YouTube channels

    图 4.43:YouTube 频道列表
    print(subs)
    

    Figure 4.44: List of Subscribers for each YouTube channel

    图 4.44:每个 YouTube 频道的订户列表
    print(views)
    

    Figure 4.45: List of Views for each YouTube channel

    图 4.45:每个 YouTube 频道的视图列表
  6. 一旦我们获得了channelssubsviews的数据,我们需要从这个给定的数据构建一个数据框架。这可以通过熊猫提供的pd.DataFrame()功能来实现。

    data = pd.DataFrame({'YouTube Channels': channels + channels, 'Subscribers in millions': subs + views, 'Type': ['Subscribers'] * len(subs) + ['Views'] * len(views)})
    
  7. 现在,既然我们有了数据框架,我们需要使用 Seaborn 提供的FacetGrid()函数创建一个 FacetGrid。这里,data是我们从上一步获得的数据帧。

    sns.set()
    g = sns.FacetGrid(data, col='Type', hue='Type', sharex=False, height=8)
    g.map(sns.barplot, 'Subscribers in millions', 'YouTube Channels')
    plt.show()
    

我们可以得出结论,PewDiePie 的 YouTube 频道拥有最高的订户数量,而 T-Series 拥有最高的浏览量。

活动 25:线性回归

解决方案:

让我们通过使用由 Seaborn 库提供的regplot()函数来可视化回归图中最大寿命和体重之间的线性关系:

  1. Lesson04文件夹打开activity25_solution.ipynb Jupyter 笔记本,执行本活动。导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    %matplotlib inline
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
  3. 使用熊猫的read_csv()功能读取数据文件夹中的数据:

    mydata = pd.read_csv("./data/anage_data.csv")
    
  4. 过滤数据,这样你最终得到的样本就包含了一个体重和最长寿命。只考虑Mammalia级和体重低于 20 万的样本。这个预处理可以在下面的代码中看到:

    longevity = 'Maximum longevity (yrs)'
    mass = 'Body mass (g)'
    data = mydata[mydata['Class'] == 'Mammalia']
    data = data[np.isfinite(data[longevity]) & np.isfinite(data[mass]) & (data[mass] < 200000)]
    
  5. 预处理完成后,我们需要使用 Seaborn 库提供的regplot()函数绘制数据。在下面的代码中,我们在regplot()函数中提供了三个参数。前两个参数为masslongevity,其中 x 轴显示体重数据,y 轴显示最大寿命数据。在第三个参数中,我们需要提供一个名为data的数据帧,它是我们从上一步中获得的:

    # Create figure
    sns.set()
    plt.figure(figsize=(10, 6), dpi=300)
    # Create scatter plot
    sns.regplot(mass, longevity, data=data)
    # Show plot
    plt.show()
    

我们可以得出结论,Mammalia级的体重和最大寿命之间存在线性关系。

活动 26:重新审视水的使用

解决方案:

让我们通过使用树图来可视化用水量,树图可以在 Squarify 库的帮助下创建:

  1. Lesson 04文件夹打开activity26_solution.ipynb Jupyter 笔记本,执行本活动。导航到该文件的路径,并在命令行终端键入以下内容:jupyter-lab

  2. 导入必要的模块,并在 Jupyter 笔记本中进行绘图:

    %matplotlib inline
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    import squarify
    
  3. 使用熊猫的read_csv()功能读取数据文件夹中的数据:

    mydata = pd.read_csv("./data/water_usage.csv")
    
  4. 通过访问前面数据集中的每一列来创建标签列表。这里,astype('str')函数用于将提取的数据转换为类型字符串。

    # Create figure
    plt.figure(dpi=200)
    # Create tree map
    labels = mydata['Usage'] + ' (' + mydata['Percentage'].astype('str') + '%)'
    
  5. 为了创建给定数据的树形图可视化,我们使用了squarify库的plot()功能。这个函数有三个参数。第一个参数是所有百分比的列表,第二个参数是所有标签的列表,这是我们在上一步中得到的。第三个参数是可以使用 Seaborn 库的light_pallete()功能创建的颜色。

    squarify.plot(sizes=mydata['Percentage'], label=labels, color=sns.light_palette('green', mydata.shape[0]))
    plt.axis('off')
    # Add title
    plt.title('Water usage')
    # Show plot
    plt.show()
    

第 5 章:绘制地理空间数据

活动 27:在地图上绘制地理空间数据

解决方案:

让我们在地图上绘制地理空间数据,并找到欧洲人口超过 10 万的城市的人口密集区:

  1. Lesson05文件夹打开activity27.ipynb Jupyter 笔记本,执行本活动。

  2. 在开始处理数据之前,我们需要导入依赖项:

    # importing the necessary dependencies
    import numpy as np
    import pandas as pd
    import geoplotlib
    
  3. Load the dataset using pandas:

    #loading the Dataset (make sure to have the dataset downloaded)
    Dataset = pd.read_csv('./data/world_cities_pop.csv', dtype = {'Region': np.str})
    

    注意

    如果我们导入数据集时没有将Region列的dtype定义为String,我们将收到一条警告,告诉我们它具有混合数据类型。我们可以通过显式定义此列中值的类型来消除此警告,这可以通过使用dtype参数来实现。

  4. To see the dtype of each column, we can use the dtypes attribute of a DataFrame:

    # looking at the data types of each column
    Dataset.dtypes
    

    下图显示了前面代码的输出:

    Figure 5.18: Datatypes of each column of the dataset

    图 5.18:数据集每一列的数据类型

    注意

    在这里,我们可以看到每一列的数据类型。由于String类型不是一个原始数据类型,它被显示为一个对象。

  5. We use the head() method of pandas DataFrames to display the first five entries:

    # showing the first 5 entries of the dataset
    dataset.head()
    

    下图显示了前面代码的输出:

    Figure 5.19: First five entries of the dataset

    图 5.19:数据集的前五个条目
  6. 大多数数据集不会是您想要的格式。其中一些可能将它们的LatitudeLongitude值隐藏在不同的列中。这就是需要第一章数据可视化和数据探索的数据角力技巧的地方。对于给定的数据集,转换很简单——我们只需要将LatitudeLongitude列映射到latlon列,这是通过赋值完成的:

    # mapping Latitude to lat and Longitude to lon
    dataset['lat'] = dataset['Latitude']
    dataset['lon'] = dataset['Longitude']
    
  7. Our dataset is now ready for the first plotting. We'll use a DotDensityLayer here, which is a good way to start and see all of our data points:

    # plotting the whole dataset with dots
    geoplotlib.dot(dataset)
    geoplotlib.show()
    

    下图显示了前面代码的输出:

    Figure 5.20: Dot-density visualization of all the cities

    图 5.20:所有城市的点密度可视化
  8. Before we start breaking down our data to get a better and more workable dataset, we want to understand the outlines of our full data. We'll do this by displaying the number of countries and the number of cities that our dataset holds:

    # amount of countries and cities
    print(len(dataset.groupby(['Country'])), 'Countries')
    print(len(dataset), 'Cities')
    

    下图显示了前面代码的输出:

    Figure 5.21: Grouping by countries and cities

    图 5.21:按国家和城市分组
  9. To see each grouped element on its own, we use the size() method, which returns a Series object:

    # amount of cities per country (first 20 entries)
    dataset.groupby(['Country']).size().head(20)
    

    下图显示了前面代码的输出:

    Figure 5.22: Number of cities per country

    图 5.22:每个国家的城市数量
  10. We also want to display the average amount of cities per country. Aggregation is an important concept in pandas that helps us in achieving this:

```py
# average num of cities per country
dataset.groupby(['Country']).size().agg('mean')
```

下图显示了前面代码的输出:

![Figure 5.23: Average amount of cities per country ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_05_23.jpg)

###### 图 5.23:每个国家的平均城市数量
  1. We now want to reduce the amount of data we are working with. One approach would be to remove all cities that don't have a population value:
#### 注意

分解和过滤你的数据是获得好的洞察力的最重要的方面之一。杂乱的可视化可以隐藏信息。

```py
# filter for countries with a population entry (Population > 0)
dataset_with_pop = dataset[(dataset['Population'] > 0)]
print('Full dataset:', len(dataset))
print('Cities with population information:', len(dataset_with_pop))
```

下图显示了前面代码的输出:

![Figure 5.24: Cities with population information ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_05_24.jpg)

###### 图 5.24:有人口信息的城市
  1. Displaying the first five items from the new dataset gives us a basic indication of what values will be present in the Population column:
```py
# displaying the first 5 items from dataset_with_pop
dataset_with_pop.head()
```

下图显示了前面代码的输出:

![Figure 5.25: First five items of the reduced dataset ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_05_25.jpg)

###### 图 5.25:缩减数据集的前五项
  1. Now. we can take a look at our reduced dataset with the help of a dot-densityplot:
```py
# showing all cities with a defined population with a dot density plot
geoplotlib.dot(dataset_with_pop)
geoplotlib.show()
```

在新的点图中,我们已经可以感受到清晰度方面的一些改进。我们的地图上仍然有太多的点。给定活动定义,我们可以通过只查看人口超过 10 万的城市来进一步过滤数据集。
  1. To further filter our dataset, we can use the same approach that we used previously:
```py
# dataset with cities with population of >= 100k
dataset_100k = dataset_with_pop[(dataset_with_pop['Population'] >= 100_000)]
print('Cities with a population of 100k or more:', len(dataset_100k))
```

下图显示了前面代码的输出:

![Figure 5.26: Cities with a population of 100k or more ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_05_26.jpg)

###### 图 5.26:人口超过 10 万的城市
  1. In addition to just plotting our 100k dataset, we also want to fix our viewport to a specific bounding box. Since our data is spread across the world, we can use the in-built WORLD constant of the BoundingBox class:
```py
# displaying all cities >= 100k population with a fixed bounding box (WORLD) in a dot density plot
from geoplotlib.utils import BoundingBox
geoplotlib.dot(dataset_100k)
geoplotlib.set_bbox(BoundingBox.WORLD)
geoplotlib.show()
```

下图显示了前面代码的输出:

![Figure 5.27: Dot-density visualization of cities with a population of 100k or more ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_05_27.jpg)

###### 图 5.27:人口超过 10 万的城市的点密度可视化
  1. Comparing it with the previous plots, it gives us a better view on where large amounts of cities are with a population of more than 100k. We now want to find the areas of these cities that are the most densely packed. As we've seen in the previous exercise, Voronoi is convenient for such insights:
```py
# using filled voronoi to find dense areas
geoplotlib.voronoi(dataset_100k, cmap='hot_r', max_area=1e3, alpha=255)
geoplotlib.show()
```

结果可视化正是我们所寻找的。在沃罗诺伊绘图中,我们可以看到明显的倾向。德国、英国、尼日利亚、印度、日本、爪哇、美国东海岸和巴西脱颖而出。我们现在可以过滤我们的数据,只看那些国家,找到最适合的。

#### 注意

您也可以使用`ColorMap`类创建自定义颜色映射渐变。
  1. The last step is to reduce our dataset to countries in Europe, such as Germany and Great Britain. We can use operators when supplying conditions to filter our data. This operator will allow us to filter for Germany and Great Britain at the same time:
```py
# filter 100k dataset for cities in Germany and GB
dataset_europe = dataset_100k[(dataset_100k['Country'] == 'de') | (dataset_100k['Country'] == 'gb')]
print('Cities in Germany or GB with population >= 100k:', len(dataset_europe))
```

![Figure 5.28: Cities in Germany and Great Britain with a population of 100K or more ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_05_28.jpg)

###### 图 5.28:德国和英国人口超过 10 万的城市
  1. Using Delaunay triangulation to find the area with the most densely packed cities, we can see one more hotspot. The areas around Cologne, Birmingham, and Manchester really stick out:
```py
#using Delaunay triangulation to find the most densely populated area
geoplotlib.delaunay(dataset_europe, cmap='hot_r')
geoplotlib.show()
```

通过使用`hot_r`彩色地图,我们可以快速获得良好的视觉表现,并使感兴趣的区域突出。

恭喜你!您已经使用 Geoplotlib 完成了第一项活动。我们利用不同的绘图来获得我们需要的信息。在接下来的活动中,我们将查看 Geoplotlib 的更多自定义功能,以更改地图切片提供程序并创建自定义绘制图层。

活动 28:使用自定义图层

解决方案:

让我们创建一个自定义图层将允许我们显示地理空间数据并随着时间的推移对数据点进行动画处理 :

  1. 在第一步中,我们只需要pandas进行数据导入:

    # importing the necessary dependencies
    import pandas as pd
    
  2. Use the read_csv method of pandas to load the .csv file:

    # loading the dataset from the csv file
    dataset = pd.read_csv('./data/flight_tracking.csv')
    

    注意

    前面的数据集可以在这里找到:https://bit.ly/2DyPHwD

  3. We need to understand the structure of our dataset by looking at the provided features:

    # displaying the first 5 rows of the dataset
    dataset.head()
    

    Figure 5.29: First five elements of the dataset

    图 5.29:数据集的前五个元素
  4. 请记住,地理图书馆需要名称为latlonlatitudelongitude列。使用熊猫中提供的rename方法重命名列:

    # renaming columns latitude to lat and longitude to lon
    dataset = dataset.rename(index=str, columns={"latitude": "lat", "longitude": "lon"})
    
  5. Taking another look at the first five elements of the dataset, we can now observe that the names of the columns have changed to lat and lon:

    # displaying the first 5 rows of the dataset
    dataset.head()
    

    Figure 5.30: Dataset with the lat and lon columns

    图 5.30:包含 lat 和 lon 列的数据集
  6. Since we want to get a visualization over time in this activity, we need to work with date and time. If we take a closer look at our dataset, it shows us that date and time are separated by two columns.

    我们需要使用to_epoch方法将日期和时间合并成一个时间戳。下面的代码可以做到这一点:

    # method to convert date and time to an unix timestamp
    from datetime import datetime
    def to_epoch(date, time):
        try:
            timestamp = round(datetime.strptime('{} {}'.format(date, time), '%Y/%m/%d %H:%M:%S.%f').timestamp())
            return timestamp
        except ValueError:
            return round(datetime.strptime('2017/09/11 17:02:06.418', '%Y/%m/%d %H:%M:%S.%f').timestamp())
    
  7. 使用前面的方法,我们现在可以使用 pandas DataFrame 提供的apply方法创建一个名为timestamp的新列,该列保存 unix 时间戳:

    # creating a new column called timestamp with the to_epoch method applied
    dataset['timestamp'] = dataset.apply(lambda x: to_epoch(x['date'], x['time']), axis=1)
    
  8. Taking another look at our dataset, we now have a new column that holds the unix timestamps:

    # displaying the first 5 rows of the dataset
    dataset.head()
    

    Figure 5.31: Dataset with a timestamp column added

    图 5.31:添加了时间戳列的数据集

    由于我们的数据集现在已经准备好与所有必要的列一起使用,我们可以开始编写我们的自定义层了。一旦到达数据集中提供的timestamp,该图层将显示每个点。它将显示几秒钟,然后消失。我们需要跟踪定制层中的当前时间戳。巩固我们在本主题理论部分所学的内容,我们有一个__init__方法来构建我们的定制TrackLayer

  9. In our draw method, we filter our dataset for all the elements that are in the mentioned time range and use each element of the filtered list to display it on the map with color that's provided by the colorbrewer method.

    由于我们的数据集只包含来自特定时间范围的数据,并且我们总是递增时间,所以我们想要检查在当前时间戳之后是否还有任何带有timestamps的元素。如果没有,我们希望将当前时间戳设置为数据集中可用的最早时间戳。下面的代码显示了我们如何创建自定义图层:

    # custom layer creation
    import geoplotlib
    from geoplotlib.layers import BaseLayer
    from geoplotlib.core import BatchPainter
    from geoplotlib.colors import colorbrewer
    from geoplotlib.utils import epoch_to_str, BoundingBox
    class TrackLayer(BaseLayer):
        def __init__(self, dataset, bbox=BoundingBox.WORLD):
            self.data = dataset
            self.cmap = colorbrewer(self.data['hex_ident'], alpha=200)
            self.time = self.data['timestamp'].min()
            self.painter = BatchPainter()
            self.view = bbox
        def draw(self, proj, mouse_x, mouse_y, ui_manager):
            self.painter = BatchPainter()
            df = self.data.where((self.data['timestamp'] > self.time) & (self.data['timestamp'] <= self.time + 180))
            for element in set(df['hex_ident']):
                grp = df.where(df['hex_ident'] == element)
                self.painter.set_color(self.cmap[element])
                x, y = proj.lonlat_to_screen(grp['lon'], grp['lat'])
                self.painter.points(x, y, 15, rounded=True)
            self.time += 1
            if self.time > self.data['timestamp'].max():
                self.time = self.data['timestamp'].min()
            self.painter.batch_draw()
            ui_manager.info('Current timestamp: {}'.format(epoch_to_str(self.time)))
    
        # bounding box that gets used when layer is created
        def bbox(self):
            return self.view
    
  10. 由于我们的数据集只包含来自英国利兹周边地区的数据,因此我们需要定义一个自定义BoundingBox,将我们的视图集中在该地区:

```py
# bounding box for our view on Leeds
from geoplotlib.utils import BoundingBox
leeds_bbox = BoundingBox(north=53.8074, west=-3, south=53.7074 , east=0)
```
  1. 地质图书馆有时要求你提供一个DataAccessObject而不是熊猫数据框。Geoplotlib 提供了一种将任何熊猫数据帧转换成DataAccessObject :
```py
# displaying our custom layer using add_layer
from geoplotlib.utils import DataAccessObject
data = DataAccessObject(dataset)
geoplotlib.add_layer(TrackLayer(data, bbox=leeds_bbox))
geoplotlib.show()
```

的便捷方法

恭喜你!您已经使用 Geoplotlib 完成了自定义图层活动。我们已经应用了几个预处理步骤来按照我们想要的方式塑造数据集。我们还编写了一个自定义图层来显示时间空间中的空间数据。我们的自定义层甚至有一个动画级别。这是我们将在下一章详细讨论的内容。

第六章:让事物与 Bokeh 互动

活动 29:使用小部件扩展绘图

解决方案:

  1. Lesson06文件夹打开activity29_solution.ipynb Jupyter 笔记本,执行本活动。

  2. 导入必要的库:

    # importing the necessary dependencies
    import pandas as pd
    
  3. 同样,我们希望在 Jupyter 笔记本中显示我们的绘图,因此我们必须从 Bokeh 的io界面导入并调用output_notebook方法:

    # make bokeh display figures inside the notebook
    from bokeh.io import output_notebook
    output_notebook()
    
  4. 下载数据集并移动到本章的数据文件夹后,我们可以导入我们的olympia2016_athletes.csv数据:

    # loading the Dataset with geoplotlib
    dataset = pd.read_csv('./data/olympia2016_athletes.csv')
    
  5. A quick test by calling head on our DataFrame shows us that our data has been successfully loaded:

    # looking at the dataset
    dataset.head()
    

    下图显示了前面代码的输出:

Figure 6.25: Loading the top five rows of the olympia2016_athletes dataset using the head method

图 6.25:使用 head 方法加载奥林匹亚 2016 _ 运动员数据集的前五行

构建交互式可视化

  1. 为了创建我们的可视化,我们需要一些额外的导入。我们将再次导入figure并从绘图界面显示,这将为我们提供创建绘图所需的工具。正如我们在前面的练习中看到的,小部件来自ipywidgets库。在这里,我们将再次使用interact作为decorator,以及widgets界面,该界面使我们能够访问不同的小部件:

    # importing the necessary dependencies 
    from bokeh.plotting import figure, show
    from ipywidgets import interact, widgets
    
  2. 和前面的练习一样,我们需要先做一些数据提取。在本活动中,我们将需要来自数据集的独特国家列表、每个国家的运动员数量以及每个国家获得的奖牌数量,分为金、银和铜:

    # extract countries and group Olympians by country and their sex
    # and the number of medals per country by sex
    countries = dataset['nationality'].unique()
    athletes_per_country = dataset.groupby('nationality').size()
    medals_per_country = dataset.groupby('nationality')['gold', 'silver','bronze'].sum()
    
  3. 在我们进入并实现这个可视化的绘图之前,我们想要设置我们的小部件和@interact方法,该方法稍后将在执行时显示绘图。执行这个空的get_plot()方法单元,然后继续创建小部件。我们稍后将实现这一点。

  4. 在本活动中,我们将使用两个IntSlider小部件来控制一个国家允许拥有的运动员和/或奖牌数量的最大数量,以便在可视化中显示。我们需要两个值来设置小部件:所有国家的最大奖牌数量和所有国家的最大运动员数量:

    # getting the max amount of medals and athletes of all countries
    max_medals = medals_per_country.sum(axis=1).max()
    max_athletes = athletes_per_country.max() 
    
  5. 使用这些最大值作为两个小部件的最大值将为我们提供合理的滑块值,如果我们应该增加数据集中运动员或奖牌的数量,这些值将被动态调整。我们需要两个IntSlider对象来处理我们的max_athletesmax_medals的输入。为了看起来像我们实际的可视化,我们希望max_athletes_slider以垂直方向显示,而max_medals_slider以水平方向显示。在可视化中,它们应该显示为Max. AthletesMax. Medals :

    # setting up the interaction elements
    max_athletes_slider=widgets.IntSlider(value=max_athletes, min=0, max=max_athletes, step=1, description='Max. Athletes:', continuous_update=False, orientation='vertical', layout={'width': '100px'})
    max_medals_slider=widgets.IntSlider(value=max_medals, min=0, max=max_medals, step=1, description='Max. Medals:', continuous_update=False, orientation='horizontal')
    
  6. 设置好小部件后,我们可以实现每次更新交互小部件时都会调用的方法。正如我们在前面的练习中看到的,我们将为此使用@interact装饰器。我们将在装饰器中提供已经创建的小部件的变量名,而不是值范围或列表。由于我们已经设置了将返回一个图的空方法,所以一旦从get_plot方法:

    # creating the interact method 
    @interact(max_athletes=max_athletes_slider, max_medals=max_medals_slider)
    def get_olympia_stats(max_athletes, max_medals):
        show(get_plot(max_athletes, max_medals))
    

    返回结果,我们就可以调用其中的方法调用来显示结果

  7. 正如我们在前面的练习中提到的,我们还可以利用这里描述的小部件:https://ipywidgets . read the docs . io/en/stable/examples/Widget % 20 list . html

  8. 一旦您构建了小部件,在执行时,您将看到它们显示在单元格下面。我们现在准备向上滚动,并与 Bokeh 一起实现绘制。

  9. The two arguments we get passed are max_athletes and max_medals. Both of them are int values. First, we want to filter down our countries dataset that contains all the countries that placed athletes in the Olympic Games. We need to check whether they have less than or equal medals and athletes than our max values, passed as arguments. Once we have a filtered down dataset, we can create our DataSource. This DataSource will be used both for the tooltips and the printing of the circle glyphs.

    注意

    有大量关于如何使用和设置工具提示的文档,您可以通过以下链接访问这些文档:https://bokeh . pydata . org/en/latest/docs/user _ guide/tools . html

  10. 使用具有以下属性的figure方法创建新的绘图:'Rio Olympics 2016 - Medal comparison'的标题、'Number of Medals'x_axis_label'Num of Athletes'y_axis_label:

```py
# creating the scatter plot
def get_plot(max_athletes, max_medals):
filtered_countries=[]
for country in countries:
if (athletes_per_country[country] <= max_athletes and 
medals_per_country.loc[country].sum() <= max_medals):
filtered_countries.append(country)
data_source=get_datasource(filtered_countries)
TOOLTIPS=[ ('Country', '@countries'),('Num of Athletes', '@y'),('Gold', '@gold'),('Silver', '@silver'),('Bronze', '@bronze')]
plot=figure(title='Rio Olympics 2016 - Medal comparison', x_axis_label='Number of Medals', y_axis_label='Num of Athletes', plot_width=800, plot_height=500, tooltips=TOOLTIPS)
plot.circle('x', 'y', source=data_source, size=20, color='color', alpha=0.5)
return plot  
```
  1. 为了用不同的颜色显示每个国家,我们希望随机创建六位数十六进制代码的颜色。以下方法可以做到这一点:
```py
# get a 6 digit random hex color to differentiate the countries better
import random	
def get_random_color():
return '%06x' % random.randint(0, 0xFFFFFF)
```
  1. 我们将使用一个 Bokeh ColumnDataSource来处理我们的数据,并使它容易被我们的工具提示和字形访问。由于我们想在工具提示中显示附加信息,我们需要我们的数据源有color字段,它保存所需数量的随机颜色;countries字段,保存过滤后的国家列表;goldsilverbronze字段,分别保存每个国家的goldsilverbronze奖牌数量;x栏,保存每个country的奖牌总数;以及y场地,该场地容纳每个country的运动员人数:
```py
# build the DataSource
def get_datasource(filtered_countries):
return ColumnDataSource(data=dict(
color=[get_random_color() for _ in filtered_countries],
countries=filtered_countries,
gold=[medals_per_country.loc[country]['gold'] for country in filtered_countries],
silver=[medals_per_country.loc[country]['silver'] for country in filtered_countries],
bronze=[medals_per_country.loc[country]['bronze'] for country in 
x=[medals_per_country.loc[country].sum() for country in filtered_countries],
y=[athletes_per_country.loc[country].sum() for country in filtered_countries]
))
```
  1. 实现完成后,我们可以再次使用@interact装饰器执行最后一个单元格。这一次,它将显示我们的散点图和我们的交互小部件。我们会看到每个国家都有不同的颜色。悬停在它们上方,我们将获得关于每个国家的更多信息,例如其简称、运动员人数以及他们获得的金、银和铜牌数量。生成的可视化应该如下所示:

图 6.26:显示散点图的最终交互式可视化

您已经构建了一个完整的可视化来显示和探索 2016 年奥运会的数据。我们在可视化中添加了两个小部件,允许我们过滤显示的国家。正如我们之前提到的,当使用交互功能和 Bokeh 时,您可能想多读一点关于 Bokeh Server 的内容。它会给你更多的选择来表达你的创造力,通过创建动画绘图和可视化,可以由几个人同时探索。

第七章:结合我们所学的知识

活动 30:在纽约市数据库上实现 Matplotlib 和 Seaborn

解决方案:

  1. Lesson07文件夹中打开 Jupyter 笔记本activity30_solution.ipynb执行本活动。导入所有必要的库:

    # Import statements
    import pandas as pd
    import numpy as np
    import seaborn as sns
    import matplotlib
    import matplotlib.pyplot as plt
    import squarify
    sns.set()
    
  2. 使用熊猫读取位于data子目录

    p_ny = pd.read_csv('./data/pny.csv')
    h_ny = pd.read_csv('./data/hny.csv')
    

    的两个.csv文件

  3. 使用给定的 PUMA(基于 2010 年人口普查定义的公共使用微数据区域代码,即人口达到或超过 100,000 的区域)范围,进一步将数据集划分为纽约市各区(布朗克斯、曼哈滕、斯塔登岛、布鲁克林和皇后区):

    # PUMA ranges
    bronx = [3701, 3710]
    manhatten = [3801, 3810]
    staten_island = [3901, 3903]
    brooklyn = [4001, 4017]
    queens = [4101, 4114]
    nyc = [bronx[0], queens[1]]
    def puma_filter(data, puma_ranges):
        return data.loc[(data['PUMA'] >= puma_ranges[0]) & (data['PUMA'] <= puma_ranges[1])]
    h_bronx = puma_filter(h_ny, bronx)
    h_manhatten = puma_filter(h_ny, manhatten)
    h_staten_island = puma_filter(h_ny, staten_island)
    h_brooklyn = puma_filter(h_ny, brooklyn)
    h_queens = puma_filter(h_ny, queens)
    p_nyc = puma_filter(p_ny, nyc)
    h_nyc = puma_filter(h_ny, nyc)
    
  4. 在数据集中,每个样本都有一个特定的weight,它反映了整个数据集的weight。因此,我们不能简单地计算中位数。使用下面代码中给定的weighted_median函数计算中位数:

    # Function for a 'weighted' median
    def weighted_frequency(values, weights):
      weighted_values = []
      for value, weight in zip(values, weights):
        weighted_values.extend(np.repeat(value, weight))
      return weighted_values
    def weighted_median(values, weights):
      return np.median(weighted_frequency(values, weights))
    
  5. In this subtask, we will create a plot containing multiple subplots that visualize information with regards to NYC wages. Visualize the median household income for the US, New York, New York City, and its districts. Visualize the average wage by gender for the given occupation categories for the population of NYC. Visualize the wage distribution for New York and NYC. Use the following yearly wage intervals: 10k steps between 0 and 100k, 50k steps between 100k and 200k, and >200k:

    第 07 课/活动 30/活动 30_solution.ipynb
    # Data wrangling for median housing income
    income_adjustement = h_ny.loc[0, ['ADJINC']].values[0] / 1e6
    def median_housing_income(data):
    //[…]
    h_queens_income_median = median_housing_income(h_queens)
    # Data wrangling for wage by gender for different occupation categories
    occ_categories = ['Management,\nBusiness,\nScience,\nand Arts\nOccupations', 'Service\nOccupations',
                    'Sales and\nOffice\nOccupations', 'Natural Resources,\nConstruction,\nand Maintenance\nOccupations',
                     'Production,\nTransportation,\nand Material Moving\nOccupations']
    //[…]
    wages_female = wage_by_gender_and_occupation(p_nyc, 2)
    # Data wrangling for wage distribution
    wage_bins = {'<$10k': [0, 10000], '$10-20k': [10000, 20000], '$20-30k': [20000, 30000], '$30-40k': [30000, 40000], '$10-20k': [40000, 50000],
                '$50-60k': [50000, 60000], '$60-70k': [60000, 70000], '$70-80k': [70000, 80000], '$80-90k': [80000, 90000], '$90-100k': [90000, 100000],
                '$100-150k': [100000, 150000], '$150-200k': [150000, 200000], '>$200k': [200000, np.infty]}
    //[…]
    wages_ny = wage_frequency(p_ny)
    # Create figure with four subplots
    fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(7, 10), dpi=300)
    # Median household income in the US
    us_income_median = 60336
    # Median household income
    ax1.set_title('Median Household Income', fontsize=14)
    //[…]
    ax1.set_xlabel('Yearly household income in $')
    # Wage by gender in common jobs
    ax2.set_title('Wage by Gender for different Job Categories', fontsize=14)
    x = np.arange(5) + 1
    //[…]
    ax2.set_ylabel('Average Salary in $')
    # Wage distribution
    ax3.set_title('Wage Distribution', fontsize=14)
    x = np.arange(len(wages_nyc)) + 1
    width = 0.4
    //[…]
    ax3.vlines(x=9.5, ymin=0, ymax=15, linestyle='--')
    # Overall figure
    fig.tight_layout()
    plt.show()
    

    https://bit.ly/2StchfL

    下图显示了前面代码的输出:

    Figure 7.15: Wage statistics for New York City in comparison with New York and the United States

    图 7.15:与纽约和美国相比,纽约市的工资统计数据
  6. Use a tree map to visualize the percentage for the given occupation subcategories for the population of NYC:

    第 07 课/活动 30/活动 30_solution.ipynb
    # Data wrangling for occupations
    occ_subcategories = {'Management,\nBusiness,\nand Financial': [10, 950],
    //[..]                    
    def occupation_percentage(data):
        percentages = []
        overall_sum = np.sum(data.loc[(data['OCCP'] >= 10) & (data['OCCP'] <= 9750), ['PWGTP']].values)
        for occ in occ_subcategories.values():
            query = data.loc[(data['OCCP'] >= occ[0]) & (data['OCCP'] <= occ[1]), ['PWGTP']].values
            percentages.append(np.sum(query) / overall_sum)
        return percentages
    occ_percentages = occupation_percentage(p_nyc)
    # Visualization of tree map
    plt.figure(figsize=(16, 6), dpi=300)
    //[..]
    plt.axis('off')
    plt.title('Occupations in New York City', fontsize=24)
    plt.show()
    

    https://bit.ly/2StchfL

    下图显示了前面代码的输出:

    Figure 7.16: Occupations in NYC

    图 7.16:纽约市的职业
  7. Use a heatmap to show the correlation between difficulties (self-care difficulty, hearing difficulty, vision, difficulty, independent living difficulty, ambulatory difficulty, veteran service-connected disability, and cognitive difficulty) and age groups (<5, 5-11, 12-14, 15-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65-74, and 75+) in New York City:

    # Data wrangling for New York City population difficulties
    difficulties = {'Self-care difficulty': 'DDRS', 'Hearing difficulty': 'DEAR',
                   'Vision difficulty': 'DEYE', 'Independent living difficulty': 'DOUT',
                   'Ambulatory difficulty': 'DPHY', 'Veteran service connected disability': 'DRATX',
                   'Cognitive difficulty': 'DREM'}
    age_groups = {'<5': [0, 4], '5-11': [5, 11], '12-14': [12, 14], '15-17': [15, 17], '18-24': [18, 24], '25-34': [25, 34],
                 '35-44': [35, 44], '45-54': [45, 54], '55-64': [55, 64], '65-74': [65, 74], '75+': [75, np.infty]}
    def difficulty_age_array(data):
        array = np.zeros((len(difficulties.values()), len(age_groups.values())))
        for d, diff in enumerate(difficulties.values()):
            for a, age in enumerate(age_groups.values()):
                age_sum = np.sum(data.loc[(data['AGEP'] >= age[0]) & (data['AGEP'] <= age[1]), ['PWGTP']].values)
                query = data.loc[(data['AGEP'] >= age[0]) & (data['AGEP'] <= age[1]) & (data[diff] == 1), ['PWGTP']].values
                array[d, a] = np.sum(query) / age_sum
        return array
    array = difficulty_age_array(p_nyc)
    # Heatmap
    plt.figure(dpi=300)
    ax = sns.heatmap(array * 100)
    ax.set_yticklabels(difficulties.keys(), rotation=0)
    ax.set_xticklabels(age_groups.keys(), rotation=90)
    ax.set_xlabel('Age Groups')
    ax.set_title('Percentage of NYC population with difficulties', fontsize=14)
    plt.show()
    

    下图显示了前面代码的输出:

Figure 7.17: Percentage of NYC population with difficulties

图 7.17:纽约市困难人口的百分比

活动 31:Bokeh 股价可视化

解决方案:

  1. Lesson07文件夹打开activity31_solution.ipynb Jupyter 笔记本,执行本活动。导入熊猫库:

    # importing the necessary dependencies
    import pandas as pd
    
  2. 在 Jupyter 笔记本中显示我们的绘图。我们要从博凯的io界面导入调用output_notebook方法:

    # make bokeh display figures inside the notebook
    from bokeh.io import output_notebook
    output_notebook()
    
  3. 下载数据集并移动到本章的数据文件夹后,我们可以导入我们的stock_prices.csv数据:

    # loading the Dataset with geoplotlib
    dataset = pd.read_csv('./data/stock_prices.csv')
    
  4. A quick test by calling head on our DataFrame shows us that our data has been successfully loaded:

    # looking at the dataset
    dataset.head()
    

    下图显示了前面代码的输出:

    Figure 7.18: Head of our imported data

    图 7.18:我们导入数据的负责人
  5. 由于date列没有关于小时、分钟和秒的信息,我们希望避免稍后在可视化中显示它们,而只显示年、月和日。因此,我们将创建一个保存日期值的格式化短版本的新列。单元格的执行将需要一些时间,因为它是一个相当大的数据集。请耐心等待:

    # mapping the date of each row to only the year-month-day format
    from datetime import datetime
    def shorten_time_stamp(timestamp):
      shortened = timestamp[0]  
      if len(shortened) > 10:
        parsed_date=datetime.strptime(shortened, '%Y-%m-%d %H:%M:%S')
        shortened=datetime.strftime(parsed_date, '%Y-%m-%d')  
      return shortened
    dataset['short_date'] = dataset.apply(lambda x: shorten_time_stamp(x), axis=1)
    
  6. Taking another look at our updated dataset, we can see a new column called short_date that holds the date without the hour, minute, and second information:

    # looking at the dataset with shortened date
    dataset.head()
    

    下图显示了前面代码的输出:

Figure 7.19: Dataset with added short_date column

图 7.19:添加了短日期列的数据集

构建交互式可视化

  1. 为了创建我们的可视化,我们需要一些额外的导入。我们将再次导入figure并从绘图界面显示这一点,这将为我们提供创建绘图所需的工具。正如我们在前面的练习中看到的,小部件来自ipywidgets库。在这里,我们将再次使用@interact作为装饰器,并使用widgets界面,该界面让我们可以访问不同的小部件:

    # importing the necessary dependencies
    from bokeh.plotting import figure, show
    from ipywidgets import interact, widgets
    
  2. 在我们进入并实现绘制方法之前,我们想要设置交互性小部件。在执行绘图之前,向下滚动到显示#extracing the necessary data的单元格。仍然要确保执行下面的单元格,即使这会简单地通过并且现在什么也不做。我们想在这里开始实现我们的可视化。在下面的单元格中,我们将提取提供给小部件元素的必要数据。在第一个单元格中,我们希望提取以下信息:数据集中存在的唯一股票名称列表、2016 年所有short_dates的列表、根据 2016 年的前一个日期列表生成的唯一日期的排序列表,以及值为open-closevolume的列表。

  3. 一旦我们有了这些信息,我们就可以开始构建我们的小部件了:

    # extracing the necessary data
    stock_names=dataset['symbol'].unique()
    dates_2016=dataset[dataset['short_date'] >= '2016-01-01']['short_date']
    unique_dates_2016=sorted(dates_2016.unique())
    value_options=['open-close', 'volume']
    
  4. 给定从前面单元格提取的信息,我们现在可以定义widgets并为其提供可用的选项。正如我们在介绍中提到的,我们希望有几个交互式功能,包括两个下拉菜单,我们可以选择两个应该相互比较的股票。默认情况下,第一个下拉菜单应选择名为Compare:的 AAPL 股票,而默认情况下,第二个下拉菜单应选择名为to: :

    # setting up the interaction elements
    drp_1=widgets.Dropdown(options=stock_names,
                           value='AAPL',
                           description='Compare:')
    drp_2=widgets.Dropdown(options=stock_names,
                           value='AON', description='to:')
    

    的 AON 股票

  5. 然后,我们需要SelectionRange,这将允许我们从提取的唯一 2016 年日期列表中选择一系列日期。默认选择前 25 个日期,命名为From-To。确保禁用continuous_update参数。调整布局宽度至500px以确保日期显示正确:

    range_slider=widgets.SelectionRangeSlider(options=unique_dates_2016, 
                                              index=(0,25), 
                                              continuous_update=False,
                                              description='From-To',
                                              layout={'width': '500px'})
    
  6. Add a RadioButton group that provides the open-close and volume options. By default, open-close should be selected, named Metric:

    range_slider=widgets.SelectionRangeSlider(options=unique_dates_2016, 
                                              index=(0,25), 
                                              continuous_update=False,
                                              description='From-To',
                                              layout={'width': '500px'})
    value_radio=widgets.RadioButtons(options=value_options,
                                     value='open-close',
                                     description='Metric')  
    

    注意

    正如我们在前面的练习中提到的,我们还可以利用这里描述的小部件:https://bit.ly/2Te9jAf

  7. 设置好小部件后,我们可以实现每次更新交互小部件时都会调用的方法。正如我们在前面的练习中看到的,我们将为此使用@interact装饰器。

  8. 我们将在装饰器中提供已经创建的小部件的变量名,而不是值范围或列表。该方法将获得四个参数:stock_1stock_2datevalue。由于我们已经设置了将返回前面的绘图的空方法,所以一旦从get_stock_for_2016方法返回结果,我们就可以用内部的方法调用来调用show()来显示结果。

  9. 一旦您构建了小部件,在执行时,您将看到它们显示在单元格下面:

    # creating the interact method 
    @interact(stock_1=drp_1, stock_2=drp_2, date=range_slider,	value=value_radio)
    def get_stock_for_2016(stock_1, stock_2, date, value):
    show(get_plot(stock_1, stock_2, date, value))
    
  10. 我们现在准备向上滚动,并与 Bokeh 一起实现绘制。目前,我们在最后一个单元格中的show()不会将任何元素渲染到我们的可视化中。我们将从所谓的蜡烛棒可视化开始,它通常用于股票价格数据。

  11. The already defined method gets our plot object, a stock_name, a stock_range containing the data of only the selected date range that was defined with the widgets, and a color for the line. We will use those arguments to create the candle sticks. They basically contain a segment that creates the vertical line, and either a green or red vbar to color code whether the close price is lower than the open price. Once you have created the candles, we also want to have a continuous line running through the mean (high, low) point of each candle. So, you have to calculate the mean for every (high/low) pair and then plot those data points with a line with the given color:

```py
def add_candle_plot(plot, stock_name, stock_range, color):
inc_1 = stock_range.close > stock_range.open
dec_1 = stock_range.open > stock_range.close
w = 0.5
plot.segment(stock_range['short_date'], stock_range['high'], 
                 stock_range['short_date'], stock_range['low'], 
                 color="grey")
plot.vbar(stock_range['short_date'][inc_1], w, 
              stock_range['high'][inc_1], stock_range['close'][inc_1], 
              fill_color="green", line_color="black",
              legend=('Mean price of ' + stock_name), muted_alpha=0.2)
plot.vbar(stock_range['short_date'][dec_1], w, 
              stock_range['high'][dec_1], stock_range['close'][dec_1], 
              fill_color="red", line_color="black",
              legend=('Mean price of ' + stock_name), muted_alpha=0.2)
stock_mean_val=stock_range[['high', 'low']].mean(axis=1)
plot.line(stock_range['short_date'], stock_mean_val, 
              legend=('Mean price of ' + stock_name), muted_alpha=0.2,
              line_color=color, alpha=0.5)
```

#### 注意

请确保在这里引用 Bokeh 库中提供的示例。你可以把里面的代码改编成我们的参数:[https://bokeh . pydata . org/en/latest/docs/gallery/烛台. html](https://bokeh.pydata.org/en/latest/docs/gallery/candlestick.html) 。
  1. 执行完add_candle_plot方法后,向下滚动并再次运行@interact单元格。现在,您将看到为两个选定的股票显示的蜡烛。最后缺少的步骤是如果选择了volume值,则执行线条的绘制。
  2. 一个额外的交互功能是有一个交互图例,允许我们mute,意思是灰色出来,可视化中的每只股票:
第 07 课/活动 31/活动 31_solution.ipynb
# method to build the plot
def get_plot(stock_1, stock_2, date, value):    
//[..]
  plot.xaxis.major_label_orientation = 1
  plot.grid.grid_line_alpha=0.3
  if value == 'open-close':
    add_candle_plot(plot, stock_1_name, stock_1_range, 'blue')
    add_candle_plot(plot, stock_2_name, stock_2_range, 'orange')
  if value == 'volume':
  plot.line(stock_1_range['short_date'], stock_1_range['volume'], 
    legend=stock_1_name, muted_alpha=0.2)
  plot.line(stock_2_range['short_date'], stock_2_range['volume'], 
    legend=stock_2_name, muted_alpha=0.2,
    line_color='orange')
  plot.legend.click_policy="mute"
  return plot

https://bit.ly/2GRneWRT2】

注意

要使我们的图例具有交互性,请查看图例功能的文档:https://bokeh . pydata . org/en/latest/docs/user _ guide/interaction/legends . html

在我们的实现完成之后,我们可以用我们的@interact装饰器再次执行最后一个单元格。这一次,它将显示我们的烛台图,一旦我们切换到音量单选按钮,我们将看到在给定日期交易的音量显示。生成的可视化应该有点像这样:

Figure 7.20: Final interactive visualization that displays the candlestick plot

图 7.20:显示烛台图的最终交互式可视化

下图显示了体积图的最终交互式可视化:

Figure 7.21: Final interactive visualization that displays the volume plot

图 7.21:显示体积图的最终交互式可视化

恭喜你!

您已经构建了一个完整的可视化来显示和探索股价数据。我们在可视化中添加了几个小部件,允许我们选择要比较的股票,将显示的数据限制在特定的日期范围内,甚至显示两种不同的图表。

正如我们之前提到的,当使用交互功能和 Bokeh 时,您可能想多读一点关于 Bokeh Server 的内容。它会给你更多的选择来表达你的创造力,通过创建动画绘图和可视化,可以由几个人同时探索。

活动 32:使用 Geoplotlib 分析 Airbnb 数据

解决方案:

  1. Lesson07文件夹打开activity032_solution.ipynb Jupyter 笔记本,执行本活动。首先进口熊猫、熊猫和地质公园:

    # importing the necessary dependencies
    import numpy as np
    import pandas as pd
    import geoplotlib
    
  2. 使用熊猫的read_csv方法加载.csv文件。如果你的电脑有点慢,使用较小的数据集:

    # loading the Dataset
    dataset = pd.read_csv('./data/airbnb_new_york.csv')
    # dataset = pd.read_csv('./data/airbnb_new_york_smaller.csv')
    
  3. Understand the structure of our dataset by looking at the provided features:

    # print the first 5 rows of the dataset
    dataset.head()
    

    下图显示了前面代码的输出:

    Figure 7.22: Displaying the first five elements of the dataset

    图 7.22:显示数据集的前五个元素
  4. 请记住,地理图书馆需要名称为latlonlatitudelongitude列。因此,我们将为latlon添加新列,并为其分配相应的值列:

    # mapping Latitude to lat and Longitude to lon
    dataset['lat'] = dataset['latitude']
    dataset['lon'] = dataset['longitude']
    
  5. 当创建根据住宿价格改变颜色的颜色图时,我们需要一个值,该值可以很容易地进行比较,并检查它是否比任何其他列表都小或大。因此,我们将创建一个名为dollar_price的新列,该列将价格列的值作为浮动保存:

    # convert string of type $<numbers> to <nubmers> of type float
    def convert_to_float(x):
        try:
            value=str.replace(x[1:], ',', '')
            return float(value)
        except:
            return 0.0
    # create new dollar_price column with the price as a number
    # and replace the NaN values by 0 in the ratings column
    dataset['price'] = dataset['price'].fillna('$0.0')
    dataset['review_scores_rating'] = dataset['review_scores_rating'].fillna(0.0)
    dataset['dollar_price'] = dataset['price'].apply(lambda x: convert_to_float(x))
    
  6. This dataset has 96 columns. When working with such a huge dataset, it makes sense to think about what data we really need and create a subsection of our dataset that only holds the data we need. Before we can do that, we'll take a look at all the columns that are available and an example for that column. This will help us decide what information is suitable:

    # print the col name and the first entry per column
    for col in dataset.columns:
    print('{}\t{}'.format(col, dataset[col][0]))
    

    下图显示了前面代码的输出:

    Figure 7.23: Each column header with an example entry from the dataset

    图 7.23:每个列标题都有一个来自数据集的示例条目
  7. 目前,我们只想使用帮助我们构建所描述的可视化的字段。这些字段是idlatitude(作为lat)、longitude(作为lon)、price(在$中)和【T7:

    # create a subsection of the dataset with the above mentioned columns
    columns=['id', 'lat', 'lon', 'dollar_price', 'review_scores_rating']
    sub_data=dataset[columns]
    
  8. Taking another look at our dataset, we now have a new column that holds the unix timestamps:

    # print the first 5 rows of the dataset
    sub_data.head()
    

    下图显示了前面代码的输出:

    Figure 7.24: Displaying the first five rows after keeping only five columns

    图 7.24:仅保留五列后显示前五行
  9. Even though we know that our data holds Airbnb listings for New York City, at the moment, we have no feeling about the amount, distribution, and character of our dataset. The simplest way to get a first glance at the data is to plot every listing with a simple dot map:

    # import DataAccessObject and create a data object as an instance of that class
    from geoplotlib.utils import DataAccessObject
    data = DataAccessObject(sub_data)
    # plotting the whole dataset with dots
    geoplotlib.dot(data)
    geoplotlib.show()
    

    下图显示了前面代码的输出:

    Figure 7.25: Simple dot map created from the points

    图 7.25:由点创建的简单点图
  10. 最后一步是编写自定义图层。这里,我们要定义一个ValueLayer,它扩展了 Geoplotlib 的BaseLayer。对于提到的交互功能,我们需要一个额外的导入。pyglet为我们提供了按键操作的选项。给定数据,我们希望用当前选定属性定义的颜色绘制地图上的每个点,或者price或者rating

  11. 为了避免非描述性输出,我们还需要调整彩色地图的比例。评级在 0 到 100 之间,而价格可以高得多。使用线性(lin)等级和对数(log)价格等级会让我们对数据有更好的了解。

  12. The view (bounding box) of our visualization will be set to New York and text information with the currently selected attribute will be displayed in the upper right corner:

![Figure 7.26: jet color map scale ](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/data-vis-py/img/C12815_07_26.jpg)

###### 图 7.26:喷射彩色地图比例尺
  1. 为了给每个点分配不同的颜色,我们只需分别绘制每个点。这肯定不是最有效的解决方案,但目前可以。我们将需要以下实例变量:self.data保存数据集,self.display,保存当前选择的属性名,self.painter,保存BatchPainter类的实例,self.view,保存BoundingBoxself.cmap,保存带有jet颜色模式的颜色映射,以及 255 和 100 级别的 alpha。
  2. Inside the invalidate method, which holds the logic of projection of the data to points on the map, we have to switch between the lin and log scales, depending on the attribute that is currently selected. The color is then determined by placing the value between 0/1 and the maximum (max_val) value, which also has to be taken from the dataset based on what attribute is currently being displayed:
##### 第 07 课/活动 32/活动 32_solution.ipynb

```py
# custom layer creation
import pyglet
import geoplotlib
//[..]
class ValueLayer(BaseLayer):
    def __init__(self, dataset, bbox=BoundingBox.WORLD):
//[..]
    def invalidate(self, proj):
        # paint every point with a color that represents the currently selected attributes value
        self.painter = BatchPainter()
        max_val = max(self.data[self.display])
        scale = 'log' if self.display == 'dollar_price' else 'lin'
        for index, id in enumerate(self.data['id']):
//[..]

    def draw(self, proj, mouse_x, mouse_y, ui_manager):
        # display the ui manager info
        ui_manager.info('Use left and right to switch between the displaying of price and ratings. Currently displaying: {}'.format(self.display))
        self.painter.batch_draw()

    def on_key_release(self, key, modifiers):

//[..]
    # bounding box that gets used when layer is created
    def bbox(self):
        return self.view
```

[https://bit.ly/2VoQveT](https://bit.ly/2VoQveT)
  1. 由于我们的数据集只包含来自纽约的数据,我们希望在开始时将视图设置为纽约。因此,我们需要一个带有给定参数的BoundingBox类的实例。除了自定义的BoundingBox之外,我们还将使用我们在第 5 章,绘制地理空间数据 :
```py
# bounding box for our view on New York
from geoplotlib.utils import BoundingBox
ny_bbox = BoundingBox(north=40.897994, west=-73.999040, south=40.595581, east=-73.95040)
# displaying our custom layer using add_layer
geoplotlib.tiles_provider('darkmatter')
geoplotlib.add_layer(ValueLayer(data, bbox=ny_bbox))
geoplotlib.show()
```

中看到的`darkmatter`图块提供者
  1. 启动可视化后,我们可以看到我们的视口聚焦在纽约。每个住宿都显示一个点,该点根据价格或点击左箭头或右箭头的评级进行着色。我们可以看到,越靠近曼哈顿中心,一般的颜色越接近黄色/橙色。另一方面,在评级可视化中,我们可以看到曼哈顿中心的住宿似乎比外面的住宿评级低:

Figure 7.27: New York Airbnb dot map, colored based on the price

图 7.27:纽约 Airbnb 点地图,根据价格着色

下图显示了基于评级的彩色点图:

Figure7.28: New York Airbnb dot map, colored based on the ratings

图 7.28:纽约 Airbnb 点阵图,根据收视率进行着色

恭喜你!

您通过编写自己的自定义图层来显示和可视化遍布纽约的 Airbnb 住宿的价格和评级信息,从而创建了一个交互式可视化。正如我们现在可以看到的,为 Geoplotlib 编写定制层是一个关注您感兴趣的属性的好方法。

感谢您选修本课程,通过 Python 提高您的数据可视化技能。

posted @ 2025-10-24 09:49  绝不原创的飞龙  阅读(9)  评论(0)    收藏  举报