Python-探索性数据分析实用指南-全-

Python 探索性数据分析实用指南(全)

原文:Hands-On Exploratory Data Analysis with Python

协议:CC BY-NC-SA 4.0

零、前言

数据是数字、文本、图片、视频、对象、音频和其他实体形式的离散对象、事件和事实的集合。处理数据提供了大量的信息。但这个价值百万的问题是——我们如何从数据中获得有意义的信息?这个问题的答案是探索性数据分析 ( EDA ),这是调查数据集、阐明主题和可视化结果的过程。EDA 是一种数据分析方法,它应用各种技术来最大化对数据集的具体洞察,揭示底层结构,提取重要变量,检测异常值和异常值,测试假设,开发模型,并确定未来估计的最佳参数。这本书,用 Python 实践探索性数据分析,旨在提供关于 EDA 主要支柱的实用知识,包括数据清洗、数据准备、数据探索和数据可视化。为什么是可视化?好吧,几项研究表明,以图形形式描绘数据使复杂的统计数据分析和商业智能更具市场价值。

*您将有机会探索开源数据集,包括医疗保健数据集、人口统计数据集、泰坦尼克号数据集、葡萄酒质量数据集、汽车数据集、波士顿房价数据集和许多其他数据集。使用这些真实的数据集,您将获得理解数据、总结数据特征以及可视化数据以实现商业智能目的的实践经验。这本书期望您使用 pandas,一个处理数据的强大库,以及其他核心 Python 库,包括 NumPy、scikit-learn、SciPy、StatsModels 用于回归,Matplotlib 用于可视化。

这本书是给谁的

这本书是为任何打算分析数据的人准备的,包括学生、教师、经理、工程师、统计学家、数据分析师和数据科学家。本实践书介绍的实用概念适用于各种学科的应用,包括语言学、社会学、天文学、市场营销、商业、管理、质量控制、教育、经济学、医学、心理学、工程学、生物学、物理学、计算机科学、地球科学、化学以及任何其他需要数据分析和综合以提高知识和帮助决策过程的领域。对 Python 编程和一些统计概念的基本理解是开始阅读这本书所需要的。

这本书涵盖了什么

第 1 章探索性数据分析基础,将帮助我们学习和修改 EDA 的基础方面。我们将深入挖掘 EDA 的重要性和主要的数据分析任务,并尝试从数据中获取意义。除此之外,我们还将使用 Python 来探索不同类型的数据,包括数值数据、时间序列数据、地理空间数据、分类数据等。

第 2 章EDA 的视觉辅助工具,将帮助我们熟练使用不同的工具,将我们从调查中获得的信息可视化,并使分析更加清晰。我们将了解如何使用数据可视化工具,如箱线图、直方图、多变量图表等。尽管如此,在使用真实的数据库绘制一个有启发性的可视化图形时,我们还是会弄脏自己的手。最后,我们将研究这些情节的直观形式。

第 3 章EDA 配合个人邮箱,将帮助我们弄清楚如何从个人 Gmail 账户导入数据集,并着手分析提取的数据集。我们将对提取的数据集执行基本的 EDA 技术,包括数据加载、数据清理、数据准备、数据可视化和数据分析。

第四章数据转换,是你在数据角力中迈出第一步的地方。我们将看到如何合并数据库样式的数据帧,在索引上合并,沿轴连接,合并重叠的数据,使用分层索引重塑,以及从长格式到宽格式的透视。我们将了解在进行分析之前需要对数据集做什么,例如删除重复项、替换值、重命名轴索引、离散化和宁滨,以及检测和过滤异常值。我们将使用函数或映射、置换和随机采样来转换数据,并计算指标/虚拟变量。

第 5 章描述性统计,将教你基本的统计方法,以获得关于在表面水平上不明显的数据的见解。我们将熟悉计算数据集的方差和标准差以及计算百分位数和四分位数的方程。此外,我们将想象那些可视化的事实测量。我们将使用盒子图等工具从统计数据中获取知识。

第 6 章分组数据集,将讲述分组的雏形以及它如何改变我们的数据集,以帮助我们更好地分析它们。我们将研究不同的分组机制,这些机制将我们的数据集聚集到不同的类中,在这些类中我们可以执行聚合活动。我们还将弄清楚如何利用数据透视表和交叉表格,用可视化的方式剖析分类数据。

第七章相关性,将帮助我们理解不同因素之间的相关性,并识别不同因素之间的相关程度。我们将了解我们可以进行的不同类型的检查,以发现数据之间的关系,包括对泰坦尼克号数据集的单变量分析、双变量分析和多变量分析,以及查看辛普森悖论。我们将观察相关性如何不总是等于因果关系。

第八章时间序列分析,将帮助我们了解时间序列数据以及如何对其进行 EDA。我们将使用开放电力系统数据进行时间序列分析。

第九章假设检验和回归,将帮助我们了解假设检验以及线性、非线性和多元线性回归。我们将建立模型开发和评估的基础。我们将使用多项式回归和管道进行模型评估。

第 10 章模型开发与评估,将帮助我们了解统一的机器学习方法,讨论不同类型的机器学习算法和评估技术。此外,在本章中,我们将使用文本数据执行无监督的聚类学习任务。此外,我们将讨论模型选择和模型部署技术。

第 11 章葡萄酒质量数据的 EDA将教我们如何使用整本书所学的所有技术在葡萄酒质量数据集上执行高级 EDA。我们将导入数据集,研究变量,基于不同的兴趣点对数据进行切片,并执行数据分析。

充分利用这本书

本书中的所有 EDA 活动都基于 Python 3.x。因此,运行本书中的任何代码的首要要求是,无论操作系统如何,都要在计算机上安装 Python 3.x。Python 可以按照其官方网站上的文档安装在您的系统上:https://www.python.org/downloads/

以下是执行代码需要安装的软件:

| 书中涉及的软件/硬件 | 操作系统要求 |
| Python 3.x | Windows、macOS、Linux 或任何其他操作系统 |
| python 笔记型电脑 | 有几个选项:本地: Jupyter: https://jupyter.org/本地:https://www.anaconda.com/distribution/在线:https://colab.research.google.com/ |
| Python 库 | NumPy Pandas scikit-learn matplotlib seaborn 状态模型 |

我们主要使用 Python 笔记本来执行代码。其中一个原因是,有了它们,将代码分解成清晰的结构并动态地看到输出相对容易。在本地安装笔记本总是更安全。官方网站保存了关于如何安装它们的大量信息。但是,如果您不想麻烦,只想立即开始学习,那么谷歌 Colab 提供了一个很好的平台,您可以使用 Python 2.x 和 Python 3.x 进行编码和执行代码,并支持图形处理单元 ( 图形处理单元)和张量处理单元(TPU)。

如果您正在使用本书的数字版本,我们建议您自己键入代码或通过 GitHub 存储库访问代码(下一节中提供了链接)。这样做将帮助您避免任何与复制和粘贴代码相关的潜在错误。

下载示例代码文件

你可以从你在www.packt.com的账户下载这本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册将文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packt.com
  2. 选择“支持”选项卡。
  3. 点击代码下载。
  4. 在搜索框中输入图书的名称,并按照屏幕指示进行操作。

下载文件后,请确保使用最新版本的解压缩文件夹:

  • 视窗系统的 WinRAR/7-Zip
  • zipeg/izp/un ARX for MAC
  • 适用于 Linux 的 7-Zip/PeaZip

这本书的代码包也托管在 GitHub 上,网址为https://GitHub . com/PacktPublishing/用 python 进行实践探索性数据分析。如果代码有更新,它将在现有的 GitHub 存储库中更新。

我们还有来自丰富的图书和视频目录的其他代码包,可在【https://github.com/PacktPublishing/】获得。看看他们!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。可以在这里下载:https://static . packt-cdn . com/downloads/9781789537253 _ color images . pdf

使用的约定

本书通篇使用了许多文本约定。

CodeInText:表示文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和推特句柄。这里有一个例子:“我们使用
matplotlibseaborn库可视化了时间序列数据集。”

代码块设置如下:

import os
import numpy as np
%matplotlib inline from matplotlib
import pyplot as plt
import seaborn as sns

任何命令行输入或输出都编写如下:

> pip install virtualenv
> virtualenv Local_Version_Directory -p Python_System_Directory

粗体:表示一个新的术语、一个重要的单词或者你在屏幕上看到的单词。例如,菜单或对话框中的单词像这样出现在文本中。这里有一个例子:“时间序列数据可能包含大量的异常值

Warnings or important notes appear like this. Tips and tricks appear like this.

取得联系

我们随时欢迎读者的反馈。

一般反馈:如果你对这本书的任何方面有疑问,在你的信息主题中提到书名,发邮件给我们customercare@packtpub.com

勘误表:虽然我们已经尽了最大的努力来保证内容的准确性,但是错误还是会发生。如果你在这本书里发现了一个错误,如果你能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata,选择您的图书,点击勘误表提交链接,并输入详细信息。

盗版:如果您在互联网上遇到任何形式的我们作品的非法拷贝,如果您能提供我们的位置地址或网站名称,我们将不胜感激。请通过copyright@packt.com联系我们,并提供材料链接。

如果你有兴趣成为一名作者:如果有一个你有专长的话题,你有兴趣写或者投稿一本书,请访问authors.packtpub.com

复习

请留下评论。一旦你阅读并使用了这本书,为什么不在你购买它的网站上留下评论呢?然后,潜在的读者可以看到并使用您不带偏见的意见来做出购买决定,我们在 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们的书的反馈。谢谢大家!

更多关于 Packt 的信息,请访问packt.com。*

一、探索性数据分析基础

本章介绍的主要目的是修订探索性数据分析 ( EDA )的基础知识,它是什么,概要分析和质量评估的关键概念,EDA 的主要维度,以及 EDA 中的主要挑战和机遇。

数据包含离散对象、数字、单词、事件、事实、测量、观察甚至事物描述的集合。这样的数据是由几个学科中发生的每一个事件或过程收集和存储的,包括生物学、经济学、工程学、市场营销和其他学科。处理这些数据会得到有用的信息,处理这些信息会产生有用的知识。但一个重要的问题是:我们如何从这些数据中产生有意义和有用的信息?这个问题的答案是 EDA。EDA 是一个检查可用数据集的过程,以发现模式、发现异常、测试假设并使用统计方法检查假设。在本章中,我们将讨论执行顶级探索性数据分析以及使用一些开源数据库弄脏我们的手所涉及的步骤。

正如这里和几项研究中提到的,EDA 的主要目的是在实际进行正式建模或假设制定之前,检查哪些数据可以告诉我们。约翰·塔克将 EDA 推广到统计学家,以检查和发现数据,并创建新的假设,用于开发新的数据收集和实验方法。

在本章中,我们将学习和修改以下主题:

  • 理解数据科学
  • EDA 的意义
  • 理解数据
  • EDA 与经典分析和贝叶斯分析的比较
  • EDA 可用的软件工具
  • EDA 入门

理解数据科学

让我们指出这一点,如果你没有听说过数据科学,那么你不应该读这本书。每个人现在都在以这样或那样的方式谈论数据科学。数据科学正处于炒作的巅峰,数据科学家的技能正在发生变化。现在,数据科学家不仅需要构建一个性能模型,而且他们必须解释所获得的结果,并将结果用于商业智能。在我的演讲、研讨会和演讲中,我发现有几个人试图问我:要成为一名顶尖的数据科学家,我需要学习什么类型的技能?我需要获得数据科学博士学位吗?嗯,有一件事我可以直接告诉你,你不需要博士就能成为数据科学专家。但人们普遍认同的一点是,数据科学涉及计算机科学、数据、统计和数学的跨学科知识。数据分析有几个阶段,包括数据需求、数据收集、数据处理、数据清理、探索性数据分析、建模和算法,以及数据产品和通信。这些阶段类似于数据挖掘中的跨行业数据挖掘标准流程 ( CRISP )框架。

这里的主要内容是 EDA 阶段,因为它是数据分析和数据挖掘的一个重要方面。让我们简单了解一下这些阶段是什么:

  • 数据需求:一个组织可以有多种数据来源。理解组织需要收集、管理和存储什么类型的数据非常重要。例如,跟踪患有痴呆症的患者的睡眠模式的应用程序需要几种类型的传感器数据存储,例如睡眠数据、患者的心率、皮肤电活动和用户活动模式。所有这些数据点都是正确诊断人的精神状态所必需的。因此,这些是应用程序的强制性要求。除此之外,还需要对数据进行分类,无论是数字的还是分类的,以及存储和传播的格式。

  • 数据收集:从多个来源收集的数据必须以正确的格式存储,并传输给公司内部合适的信息技术人员。如前所述,可以使用不同类型的传感器和存储工具从几个事件的几个对象收集数据。

  • 数据处理:预处理涉及实际分析前对数据集进行预策的过程。常见的任务包括正确导出数据集,将它们放在正确的表下,对它们进行结构化,并以正确的格式导出它们。

  • 数据清理:预处理后的数据仍未做好详细分析的准备。它必须正确转换为不完整检查、重复检查、错误检查和缺失值检查。这些任务是在数据清理阶段执行的,这涉及到匹配正确的记录、查找数据集中的不准确性、了解整体数据质量、删除重复项以及填写缺失值等职责。然而,我们如何在任何数据集上识别这些异常呢?找到这样的数据问题需要我们执行一些分析技术。我们将在第 4 章数据转换中学习几种这样的分析技术。简而言之,数据清理取决于所研究的数据类型。因此,对于数据科学家或 EDA 专家来说,理解不同类型的数据集是最重要的。数据清理的一个例子是使用异常值检测方法进行定量数据清理。

  • EDA: 探索性数据分析,如前所述,是我们实际开始理解数据中包含的信息的阶段。应当指出,在勘探过程中可能需要几种类型的数据转换技术。我们将在第二节第五章描述性统计中深入讲解描述性统计,了解描述性统计背后的数学基础。这整本书致力于探索性数据分析中涉及的任务。

  • 建模与算法:从数据科学的角度来看,广义模型或数学公式可以表示或展示不同变量之间的关系,如相关性或因果关系。这些模型或方程涉及一个或多个变量,这些变量依赖于导致事件的其他变量。比如买笔的时候,笔的总价(合计)=一支笔的价格(单价)×买笔数(数量)。因此,我们的模型将是总量=单价数量。这里,总价取决于单价。因此,总价被称为因变量,单价被称为自变量。一般来说,模型总是描述自变量和因变量之间的关系。推理统计学处理特定变量之间的量化关系。
    用于描述数据、模型和误差之间关系的 Judd 模型仍然成立:
    数据=模型+误差。我们将在第 3 节第 10 章模型评估中详细讨论模型开发。推断统计的一个例子是回归分析。我们将在第 9 章回归*中讨论回归分析。

  • 数据产品:任何以数据为输入,产生输出,并根据输出提供反馈来控制环境的计算机软件都被称为数据产品。数据产品通常基于在数据分析期间开发的模型,例如,输入用户购买历史并推荐用户很可能购买的相关项目的推荐模型。

  • 沟通:此阶段处理向最终利益相关方传播结果,以便将结果用于商业智能。这一阶段最值得注意的步骤之一是数据可视化。可视化处理信息传递技术,如表格、图表、总结图和条形图,以显示分析结果。我们将在第 2 章中概述几种可视化技术,为 EDA 提供不同类型的数据。

EDA 的意义

科学、经济、工程和营销的不同领域主要在电子数据库中积累和存储数据。应该利用收集到的数据做出适当的既定决策。没有计算机程序的帮助,要理解包含大量数据点的数据集实际上是不可能的。为了确定收集的数据所提供的见解并做出进一步的决策,数据挖掘是在我们经历独特的分析过程时执行的。探索性数据分析是关键,通常是数据挖掘的第一步。它允许我们可视化数据来理解它,并为进一步的分析创建假设。探索性分析围绕着为数据挖掘项目的后续步骤创建数据或见解的概要。

EDA 实际上揭示了关于内容的基本事实,而没有做出任何潜在的假设。事实上,数据科学家使用这个过程来实际理解可以创建什么类型的建模和假设。探索性数据分析的关键组成部分包括汇总数据、统计分析和数据可视化。Python 提供专家工具进行探索性分析,用pandas进行总结;scipy,与其他一起,进行统计分析;可视化的还有matplotlibplotly

有道理,对吧?当然有。这是你看这本书的原因之一。了解了 EDA 的意义后,让我们在下一节中发现 EDA 中涉及的最通用的步骤是什么。

EDA 中的步骤

了解了 EDA 是什么及其意义之后,让我们来了解一下数据分析中涉及的各个步骤。基本上,它包括四个不同的步骤。让我们逐一了解每一个步骤,以便对每个步骤有一个简单的了解:

  • 问题定义:在尝试从数据中提取有用的洞察之前,定义要解决的业务问题至关重要。问题定义是数据分析计划执行的驱动力。问题定义中涉及的主要任务是定义分析的主要目标、定义主要可交付成果、概述主要角色和职责、获取数据的当前状态、定义时间表以及执行成本/收益分析。基于这样的问题定义,可以创建一个执行计划。
  • 数据准备:这一步涉及到实际分析前准备数据集的方法。在这一步中,我们定义数据源,定义数据模式和表,了解数据的主要特征,清理数据集,删除不相关的数据集,转换数据,并将数据分成所需的块进行分析。
  • 数据分析:这是处理数据的描述性统计和分析的最关键步骤之一。主要任务包括汇总数据、发现数据之间隐藏的相关性和关系、开发预测模型、评估模型和计算精度。用于数据汇总的一些技术是汇总表、图表、描述性统计、推理统计、相关统计、搜索、分组和数学模型。
  • 结果的开发和表示:这一步包括以图形、汇总表、地图和图表的形式向目标受众呈现数据集。这也是一个必不可少的步骤,因为从数据集分析的结果应该可以被业务利益相关者解释,这是 EDA 的主要目标之一。大多数图形分析技术包括散点图、特征图、直方图、箱线图、残差图、均值图等。我们将在第 2 章EDA 视觉辅助工具中探讨几种类型的图形表示。

理解数据

识别所分析数据的类型至关重要。在本节中,我们将了解您在分析过程中可能遇到的不同类型的数据。不同的学科为不同的目的存储不同种类的数据。例如,医学研究人员存储患者数据,大学存储学生和教师数据,房地产行业存储和构建数据集。数据集包含关于特定对象的许多观察结果。例如,一个关于医院病人的数据集可以包含许多观察结果。可以通过患者标识符(ID)、姓名、地址、体重、出生日期、地址、电子邮件、性别来描述患者。这些描述病人的特征都是一个变量。对于这些变量中的每一个,每个观察值都可以有一个特定的值。例如,患者可能有以下情况:

PATIENT_ID = 1001
Name = Yoshmi Mukhiya
Address = Mannsverk 61, 5094, Bergen, Norway
Date of birth = 10th July 2018
Email = yoshmimukhiya@gmail.com
Weight = 10 
Gender = Female

这些数据集存储在医院中,并提交进行分析。这些数据大部分存储在表/模式中的某种数据库管理系统中。此处显示了一个用于存储患者信息的表格示例:

| 患者号 | 名称 | 地址 | DOB | 电子邮件 | 性别 | 重量 |
| 001 | 苏雷什·库马尔·穆西亚 | 曼思韦克,61 岁 | 30.12.1989 | skmu@hvl 不要 | 男性的 | sixty-eight |
| 002 | 约瑟夫·穆希亚 | 曼斯维尔克 61, 5094, 卑尔根 | 10.07.2018 | yoshmimukhiya@gmail . com | 女性的 | one |
| 003 | 安朱·穆希亚 | 曼斯维尔克 61, 5094, 卑尔根 | 10.12.1997 | 昂儒瓦内@gmail.com | 女性的 | Twenty-four |
| 004 | Asha Gaire | 尼泊尔布特瓦尔 | 30.11.1990 | aasha.gaire@gmail.com | 女性的 | Twenty-three |
| 005 | 约翰·史密斯 - 维基百科,自由的百科全书 | 瑞典丹马克 | 12.12.1789 | hello @ Gmail . com | 男性的 | Seventy-five |

总结上表,有四个观察值(001,002,003,004,005)。每个观察描述变量(PatientIDnameaddressdobemailgenderweight)。大部分数据集大致分为两组——数字数据和分类数据。

数据

这些数据有一种测量的感觉;例如,一个人的年龄、身高、体重、血压、心率、体温、牙齿数量、骨骼数量和家庭成员数量。这个数据在统计学中常被称为量化数据。数字数据集可以是离散或连续类型。

离散数据

这是可计数的数据,其值可以列出来。例如,如果我们掷硬币,200 次硬币掷中的头数可以取 0 到 200(有限)的值。表示离散数据集的变量称为离散变量。离散变量采用固定数量的不同值。例如,Country变量可以有尼泊尔、印度、挪威和日本等值。它是固定的。教室中学生的Rank变量可以取 1、2、3、4、5 等数值。

连续数据

在特定范围内可以有无限多个数值的变量被归类为连续数据。描述连续数据的变量是连续变量。例如,你所在城市今天的温度是多少?我们能有限吗?同样,上一节的weight变量是连续变量。我们将使用第 5 章描述性统计中的汽车数据集来执行 EDA。

下表显示了该表的一部分:

检查上表,确定哪些变量是离散的,哪些变量是连续的。你能证明你的主张是正确的吗?连续数据可以遵循标度的区间度量或标度的比率度量。我们将在本章的测量刻度部分详细讨论。

分类数据

这种类型的数据代表一个对象的特征;例如性别、婚姻状况、地址类型或电影类别。该数据在统计学中常被称为定性数据集。为了清楚地理解,以下是您可以在数据中找到的一些最常见的分类数据类型:

  • 性别(男性、女性、其他或未知)

  • 婚姻状况(无效、离婚、中间、合法分居、已婚、一夫多妻、从未结婚、家庭伴侣、未婚、丧偶或未知)

  • 电影类型(动作、冒险、喜剧、犯罪、戏剧、奇幻、历史、恐怖、神秘、哲学、政治、浪漫、传奇、讽刺、科幻、社交、惊悚、都市或西部)

  • 血型(甲、乙、AB 或 O)

  • 药物类型(兴奋剂、镇静剂、致幻剂、解离剂、阿片类药物、吸入剂或大麻)

描述分类数据的变量称为分类变量。这些类型的变量可以有有限数量的值之一。计算机科学专业的学生更容易将分类值理解为变量的枚举类型或枚举。有不同类型的分类变量:

  • 一个二进制分类变量可以正好取两个值,也称为二分变量。例如,当你创建一个实验时,结果不是成功就是失败。因此,结果可以理解为一个二元分类变量
  • 多态变量是可以取两个以上可能值的分类变量。例如,婚姻状况可以有几种值,如无效、离婚、中间、合法分居、已婚、一夫多妻、从未结婚、家庭伴侣、未婚、丧偶、家庭伴侣和未知。因为婚姻状况可以有两个以上的可能值,所以它是一个多变量。

大多数分类数据集遵循名义或顺序测量尺度。让我们在下一节了解什么是名义或序数尺度。

测量标尺

统计学中描述了四种不同类型的测量尺度:名义尺度、序数尺度、区间尺度和比率尺度。这些量表多用于学术行业。让我们用一些例子来理解它们。

名义上的

这些是用来标注没有任何数量值的变量的。刻度一般称为标签。这些尺度是相互排斥的,没有任何数值上的重要性。让我们看一些例子:

  • 你的性别是什么?
  • 男性的
  • 女性的
  • 第三性别/非二元
  • 我不想回答
  • 其他的
  • 其他例子包括:
  • 特定国家所说的语言
  • 生物学种
  • 语法中的词类(名词、代词、形容词等)
  • 生物学中的分类等级(太古宙、细菌和真核生物)

标称标度被视为定性标度,使用定性标度进行的测量被视为定性数据。然而,定性研究的进展造成了混乱,不能肯定地认为是定性的。例如,如果有人使用数字作为名义测量意义上的标签,它们就没有具体的数值或意义。对名义上的度量不能进行任何形式的算术计算。

你可能会想为什么要关心数据是名义的还是序数的?我们不应该开始加载数据并开始分析吗?嗯,我们可以。但是想想看:你有一个数据集,你想分析它。你将如何决定你是否可以做一个饼图、条形图或直方图?你明白我的意思了吗?

例如,在名义数据集的情况下,您当然可以知道以下内容:

  • 频率是数据集内一段时间内标签出现的速率。
  • 比例可以通过频率除以事件总数来计算。
  • 然后,你可以计算每个比例的百分比
  • 为了使标称数据集可视化,可以使用饼图或条形图。

如果你知道你的数据遵循名义比例,你可以使用饼图或条形图。那就少了一件要担心的事,对吗?我的观点是,理解数据类型与理解您可以执行什么类型的计算、您应该在数据集上适合什么类型的模型以及您可以生成什么类型的可视化相关。

序数

序数标度和名义标度的主要区别在于顺序。在序数尺度中,值的顺序是一个重要因素。记住序数音阶的一个简单提示是,它听起来像是命令。你听说过李克特量表吗,它使用序数量表的变体?让我们用李克特量表来检查序数量表的一个例子: WordPress 正在让内容管理者的生活变得更容易。你对这个说法有什么看法?下图为李克特量表:

如上图所示, WordPress 问题的答案是让内容管理人员的生活更轻松缩小到五个不同的序数值,强烈同意同意中立不同意强烈不同意。像这样的量表被称为李克特量表。同样,下图显示了李克特量表的更多示例:

为了使它更容易,考虑将序数标度作为排名的顺序(第 1、第 2、第 3、第 4,等等)。允许将中值项作为中心趋势的度量;但是,平均是不允许的。

间隔

在区间尺度中,值之间的顺序和精确差异都是显著的。区间尺度在统计学中被广泛使用,例如,在中心趋势的度量中——平均值、中值、模式和标准偏差。示例包括笛卡尔坐标中的位置和从磁北以度为单位测量的方向。区间数据允许使用平均值、中值和模式。

比例

比率等级包含顺序、精确值和绝对零,这使得它可以用于描述性和推断性统计。这些尺度为统计分析提供了许多可能性。数学运算、中心趋势的度量以及离散度的度量和变量的系数 n 也可以从这样的标度中计算出来。

示例包括能量、质量、长度、持续时间、电能、平面角度和体积的度量。下表总结了数据类型和比例度量:

在下一节中,我们将比较 EDA 与经典和贝叶斯分析。

EDA 与经典分析和贝叶斯分析的比较

数据分析有几种方法。与本书相关的最受欢迎的有:

  • 经典数据分析:对于经典数据分析方法,问题定义和数据收集步骤之后是模型开发,之后是分析和结果交流。
  • 探索性数据分析方法:对于 EDA 方法,除了模型拼版和数据分析步骤互换之外,遵循与经典数据分析相同的方法。主要关注的是数据、数据结构、异常值、模型和可视化。通常,在 EDA 中,我们不会对数据强加任何确定性或概率性模型。
  • 贝叶斯数据分析方法:贝叶斯方法将先验概率分布知识融入分析步骤,如下图所示。简而言之,任何量的先验概率分布都是在考虑某些证据之前表达了对特定量的信念。你还在纠结先验概率分布这个术语吗?安德鲁·盖尔曼有一篇关于先验概率分布的描述性很强的论文。下图显示了三种不同的数据分析方法,说明了它们执行步骤的差异:

数据分析师和数据科学家可以自由地混合前面提到的步骤,从数据中获得有意义的见解。除此之外,基本上很难判断或估计哪个模型最适合数据分析。它们都有各自的范式,适用于不同类型的数据分析。

EDA 可用的软件工具

有几种软件工具可用于促进 EDA。在这里,我们将概述一些开源工具:

EDA 入门

如前所述,我们将使用 Python 作为数据分析的主要工具。耶!好吧,如果你问我为什么,Python 一直在十大编程语言中排名靠前,并被数据科学专家广泛用于数据分析和数据挖掘。在这本书里,我们假设你有 Python 的工作知识。如果你不熟悉 Python,现在开始数据分析可能还为时过早。我假设您熟悉以下 Python 工具和包:

| Python 编程 | 变量、字符串和数据类型的基本概念条件和函数序列、集合和迭代使用文件面向对象编程 |
| NumPy | 使用 NumPy 创建数组、复制数组和划分数组对 NumPy 数组执行不同的操作了解数组选择、高级索引和扩展使用多维数组线性代数函数和内置 NumPy 函数 |
| Pandas | 理解并创建DataFrame对象子集数据和索引数据算术函数和 Pandas 映射管理指数视觉分析的建筑风格 |
| Matplotlib | 加载线性数据集调整轴、网格、标签、标题和图例保存地块 |
| 我的天啊 | 正在导入包使用 SciPy 的统计软件包执行描述性统计推理和数据分析 |

在深入分析细节之前,我们需要确保我们的观点一致。让我们仔细检查清单,验证您是否满足了充分利用本书的所有先决条件:

| 设置虚拟环境 |

> pip install virtualenv
> virtualenv Local_Version_Directory -p Python_System_Directory

|
| 读取/写入文件 |

filename = "datamining.txt" 
file = open(filename, mode="r", encoding='utf-8')
for line in file: 
 lines = file.readlines()
print(lines)
file.close()

|
| 错误处理 |

try:
  Value = int(input("Type a number between 47 and 100:"))
except ValueError:
   print("You must type a number between 47 and 100!")
else:
   if (Value > 47) and (Value <= 100):
       print("You typed: ", Value)
   else:
       print("The value you typed is incorrect!")

|
| 面向对象的概念 |

class Disease:
  def __init__(self, disease = 'Depression'):
    self.type = disease

  def getName(self):
    print("Mental Health Diseases: {0}".format(self.type))

d1 = Disease('Social Anxiety Disorder')
d1.getName()

|

接下来,让我们看看使用 NumPy 库的 EDA 的基本操作。

NumPy

在本节中,我们将使用NumPy库修改 EDA 的基本操作。如果您熟悉这些操作,请随意跳到下一节。当仔细阅读代码时,可能会感觉很明显,但是在深入研究 EDA 操作之前,确保您理解这些概念是至关重要的。当我开始学习数据科学方法时,我关注了许多博客,他们只是重新塑造了一个数组或矩阵。当我运行他们的代码时,它工作得很好,但是我从来不明白我是如何添加两个不同维度的矩阵的。在本节中,我试图明确指出一些基本的numpy操作:

  • 对于导入numpy,我们将使用以下代码:
import numpy as np
  • 为了创建不同类型的numpy数组,我们将使用以下代码:
# importing numpy
import numpy as np

# Defining 1D array
my1DArray = np.array([1, 8, 27, 64])
print(my1DArray)

# Defining and printing 2D array
my2DArray = np.array([[1, 2, 3, 4], [2, 4, 9, 16], [4, 8, 18, 32]])
print(my2DArray)

#Defining and printing 3D array
my3Darray = np.array([[[ 1, 2 , 3 , 4],[ 5 , 6 , 7 ,8]], [[ 1, 2, 3, 4],[ 9, 10, 11, 12]]])
print(my3Darray)
  • 为了显示基本信息,例如 NumPy 数组的数据类型、形状、大小和步长,我们将使用以下代码:
# Print out memory address
print(my2DArray.data)

# Print the shape of array
print(my2DArray.shape)

# Print out the data type of the array
print(my2DArray.dtype)

# Print the stride of the array.
print(my2DArray.strides)
  • 为了使用内置的 NumPy 函数创建数组,我们将使用以下代码:
# Array of ones
ones = np.ones((3,4))
print(ones)

# Array of zeros
zeros = np.zeros((2,3,4),dtype=np.int16)
print(zeros)

# Array with random values
np.random.random((2,2))

# Empty array
emptyArray = np.empty((3,2))
print(emptyArray)

# Full array
fullArray = np.full((2,2),7)
print(fullArray)

# Array of evenly-spaced values
evenSpacedArray = np.arange(10,25,5)
print(evenSpacedArray)

# Array of evenly-spaced values
evenSpacedArray2 = np.linspace(0,2,9)
print(evenSpacedArray2)
  • 对于 NumPy 数组和文件操作,我们将使用以下代码:
# Save a numpy array into file
x = np.arange(0.0,50.0,1.0)
np.savetxt('data.out', x, delimiter=',')

# Loading numpy array from text
z = np.loadtxt('data.out', unpack=True)
print(z)

# Loading numpy array using genfromtxt method
my_array2 = np.genfromtxt('data.out',
                      skip_header=1,
                      filling_values=-999)
print(my_array2)
  • 为了检查 NumPy 数组,我们将使用以下代码:
# Print the number of `my2DArray`'s dimensions
print(my2DArray.ndim)

# Print the number of `my2DArray`'s elements
print(my2DArray.size)

# Print information about `my2DArray`'s memory layout
print(my2DArray.flags)

# Print the length of one array element in bytes
print(my2DArray.itemsize)

# Print the total consumed bytes by `my2DArray`'s elements
print(my2DArray.nbytes)
  • 广播是一种机制,允许 NumPy 在执行算术运算时对不同形状的数组进行操作:
# Rule 1: Two dimensions are operatable if they are equal
# Create an array of two dimension
A =np.ones((6, 8))

# Shape of A
print(A.shape)

# Create another array
B = np.random.random((6,8))

# Shape of B
print(B.shape)

# Sum of A and B, here the shape of both the matrix is same.
print(A + B)

其次,当数组的一个维度为 1 时,两个维度也是兼容的。检查这里给出的例子:

# Rule 2: Two dimensions are also compatible when one of them is 1
# Initialize `x`
x = np.ones((3,4))
print(x)

# Check shape of `x`
print(x.shape)

# Initialize `y`
y = np.arange(4)
print(y)

# Check shape of `y`
print(y.shape)

# Subtract `x` and `y`
print(x - y)

最后,还有第三条规则,如果两个数组在所有维度上都兼容,那么它们可以一起广播。检查这里给出的例子:

# Rule 3: Arrays can be broadcast together if they are compatible in all dimensions
x = np.ones((6,8))
y = np.random.random((10, 1, 8))
print(x + y)

x(6,8)y(10,1,8) 的尺寸不同。但是,可以添加它们。为什么会这样?另外,更改 y(10,2,8)y(10,1,4) 会给出ValueError。你能找出原因吗?(提示:检查规则 1)。

  • 为了了解 NumPy 数学的工作原理,我们将使用以下示例:
# Basic operations (+, -, *, /, %)
x = np.array([[1, 2, 3], [2, 3, 4]])
y = np.array([[1, 4, 9], [2, 3, -2]])

# Add two array
add = np.add(x, y)
print(add)

# Subtract two array
sub = np.subtract(x, y)
print(sub)

# Multiply two array
mul = np.multiply(x, y)
print(mul)

# Divide x, y
div = np.divide(x,y)
print(div)

# Calculated the remainder of x and y
rem = np.remainder(x, y)
print(rem)
  • 现在让我们看看如何使用索引来创建子集和分割数组:
x = np.array([10, 20, 30, 40, 50])

# Select items at index 0 and 1
print(x[0:2])

# Select item at row 0 and 1 and column 1 from 2D array
y = np.array([[ 1, 2, 3, 4], [ 9, 10, 11 ,12]])
print(y[0:2, 1])

# Specifying conditions
biggerThan2 = (y >= 2)
print(y[biggerThan2])

接下来,我们将使用pandas库从数据中获得见解。

Pandas

韦斯·麦金尼开源了在数据科学中广泛使用的pandas库(https://github.com/wesm)。我们将利用这个库从数据中获得有意义的见解。在详细研究这一部分之前,我们将重温一些你应该熟悉的 Pandas 最基本的技术,以便能够跟随即将到来的章节。如果这些东西对你来说是新的,请随意查看其中一个进一步的阅读部分,获取更多资源。请执行以下步骤:

  1. 使用以下设置默认参数:
import numpy as np
import pandas as pd
print("Pandas Version:", pd.__version__)

pd.set_option('display.max_columns', 500)
pd.set_option('display.max_rows', 500)
  1. 在 Pandas 中,我们可以通过两种方式创建数据结构:系列和数据框架。查看下面的代码片段,了解我们如何从序列、字典和 n 维数组创建数据帧。

下面的代码片段显示了我们如何从一系列数据中创建数据帧:

series = pd.Series([2, 3, 7, 11, 13, 17, 19, 23])
print(series)

# Creating dataframe from Series
series_df = pd.DataFrame({
    'A': range(1, 5),
    'B': pd.Timestamp('20190526'),
    'C': pd.Series(5, index=list(range(4)), dtype='float64'),
    'D': np.array([3] * 4, dtype='int64'),
    'E': pd.Categorical(["Depression", "Social Anxiety", "Bipolar Disorder", "Eating Disorder"]),
    'F': 'Mental health',
    'G': 'is challenging'
})
print(series_df)

下面的代码片段显示了如何为字典创建数据框:

# Creating dataframe from Dictionary
dict_df = [{'A': 'Apple', 'B': 'Ball'},{'A': 'Aeroplane', 'B': 'Bat', 'C': 'Cat'}]
dict_df = pd.DataFrame(dict_df)
print(dict_df)

下面的代码片段显示了如何从 n 维数组创建数据帧:

# Creating a dataframe from ndarrays
sdf = {
    'County':['Østfold', 'Hordaland', 'Oslo', 'Hedmark', 'Oppland', 'Buskerud'],
    'ISO-Code':[1,2,3,4,5,6],
    'Area': [4180.69, 4917.94, 454.07, 27397.76, 25192.10, 14910.94],
    'Administrative centre': ["Sarpsborg", "Oslo", "City of Oslo", "Hamar", "Lillehammer", "Drammen"]
    }
sdf = pd.DataFrame(sdf)
print(sdf)
  1. 现在,让我们从外部源加载一个数据集到 PandasDataFrame中。之后,让我们看看前 10 个条目:
columns = ['age', 'workclass', 'fnlwgt', 'education', 'education_num',
    'marital_status', 'occupation', 'relationship', 'ethnicity', 'gender','capital_gain','capital_loss','hours_per_week','country_of_origin','income']
df = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data',names=columns)
df.head(10)

如果运行前面的单元格,您应该会得到类似于下面截图的输出:

  1. 下面的代码显示了 dataframe 使用的行、列、数据类型和内存:
df.info()

前面代码片段的输出应该类似于以下内容:

# Output:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 32561 entries, 0 to 32560
Data columns (total 15 columns):
age 32561 non-null int64
workclass 32561 non-null object
fnlwgt 32561 non-null int64
education 32561 non-null object
education_num 32561 non-null int64
marital_status 32561 non-null object
occupation 32561 non-null object
relationship 32561 non-null object
ethnicity 32561 non-null object
gender 32561 non-null object
capital_gain 32561 non-null int64
capital_loss 32561 non-null int64
hours_per_week 32561 non-null int64
country_of_origin 32561 non-null object
income 32561 non-null object
dtypes: int64(6), object(9)
memory usage: 3.7+ MB
  1. 现在让我们看看如何在任何数据框中选择行和列:
# Selects a row
df.iloc[10] 

# Selects 10 rows 
df.iloc[0:10]

# Selects a range of rows 
df.iloc[10:15] 

 # Selects the last 2 rows
df.iloc[-2:] 

# Selects every other row in columns 3-5
df.iloc[::2, 3:5].head() 
  1. 让我们结合 NumPy 和 pandas 来创建一个数据帧,如下所示:
import pandas as pd
import numpy as np

np.random.seed(24)
dFrame = pd.DataFrame({'F': np.linspace(1, 10, 10)})
dFrame = pd.concat([df, pd.DataFrame(np.random.randn(10, 5), columns=list('EDCBA'))],
               axis=1)
dFrame.iloc[0, 2] = np.nan
dFrame

它应该生成一个类似于下面截图的数据帧表:

  1. 让我们用一个自定义规则来设计这个表格。如果值大于零,我们将颜色更改为黑色(默认颜色);如果该值小于零,我们将颜色更改为红色;最后,其他所有的东西都会被染成绿色。让我们定义一个 Python 函数来实现这一点:
# Define a function that should color the values that are less than 0 
def colorNegativeValueToRed(value):
  if value < 0:
    color = 'red'
  elif value > 0:
    color = 'black'
  else:
    color = 'green'

  return 'color: %s' % color
  1. 现在,让我们将这个函数传递给 dataframe。我们可以通过使用数据框内 Pandas 提供的style方法来做到这一点:
s = df.style.applymap(colorNegativeValueToRed, subset=['A','B','C','D','E'])
s

它应该显示一个彩色数据帧,如下图所示:

应该注意的是applymapapply方法计算量很大,因为它们适用于数据帧内的每个值。因此,执行起来需要一些时间。耐心等待执行。

  1. 现在,让我们再深入一步。我们希望扫描每一列,并突出显示该列中的最大值和最小值:
def highlightMax(s):
    isMax = s == s.max()
    return ['background-color: orange' if v else '' for v in isMax]

def highlightMin(s):
    isMin = s == s.min()
    return ['background-color: green' if v else '' for v in isMin] 

我们将这两个函数应用于数据帧,如下所示:

df.style.apply(highlightMax).apply(highlightMin).highlight_null(null_color='red')

输出应该类似于下面的截图:

  1. 你仍然不满意你的形象化吗?让我们尝试使用另一个名为seaborn的 Python 库,并为表格提供一个渐变:
import seaborn as sns

colorMap = sns.light_palette("pink", as_cmap=True)

styled = df.style.background_gradient(cmap=colorMap)
styled

数据框应该应用橙色渐变:

可能性无穷无尽。你如何呈现你的结果取决于你自己。请记住,当您向最终利益相关者(您的经理、老板或非技术人员)展示您的结果时,无论您的代码写得多么聪明,如果他们不能理解您的程序,对他们来说都是毫无价值的。人们普遍认为,更直观的结果很容易销售。

我的天啊

SciPy 是 Python 的科学库,是开源的。我们将在接下来的章节中使用这个库。这个库依赖于 NumPy 库,它提供了一个高效的 n 维数组操作函数。我们将在接下来的章节中了解更多关于这些库的信息。我在这里的意图只是通知你,准备面对除了 NumPy 和 Pandas 之外的其他图书馆。如果你想早点开始,可以从 SciPy 库中查询scipy.stats

Matplotlib

Matplotlib 提供了一个巨大的可定制地块库,以及一组全面的后端。它可以用来创建专业的报告应用程序、交互式分析应用程序、复杂的仪表板应用程序、web/GUI 应用程序、嵌入式视图等等。我们将在第 2 章中详细探讨 Matplotlib,V 是 EDA 的辅助工具

摘要

在这一章中,我们回顾了数据分析和探索性数据分析背后最基本的理论。EDA 是数据分析中最突出的步骤之一,涉及数据需求、数据收集、数据处理、数据清理、探索性数据分析、建模和算法、数据生产和通信等步骤。识别所分析数据的类型至关重要。不同的学科为不同的目的存储不同种类的数据。例如,医学研究人员存储患者的数据,大学存储学生和教师的数据,房地产行业存储房屋和建筑数据集,等等。数据集包含关于特定对象的许多观察结果。大部分数据集可以分为数值数据集和分类数据集。有四种类型的数据测量尺度:名义、顺序、区间和比率。

在本书中,我们将使用几个 Python 库来执行从简单到复杂的探索性数据分析,包括 NumPy、pandas、SciPy 和 Matplotlib。在下一章中,我们将学习用于探索性数据分析的各种类型的可视化辅助工具。

进一步阅读

  • 格伦·米亚特(2006 年)。让数据有意义:探索性数据分析和数据挖掘实用指南。打印 ISBN:9780470074718 |在线 ISBN:9780470101025 | DOI:10.1002/0470101024
  • 查特菲尔德特区(1995 年)。解决问题:统计员指南(第 2 版。).查普曼和霍尔。ISBN 978-0412606304。
  • 先验分布,Andrew Gelman 第 3 卷,第 1634-1637 页,http://www . stat . Columbia . edu/~ Gelman/research/published/p039-_ o . pdf
  • 希勒(2000 年)。CRISP-DM 模型:数据挖掘新蓝图。数据仓库;5:13—22.
  • 贾德,查尔斯和麦克莱伦,加里(1989 年)。数据分析。哈科特·布雷斯·约万诺维奇。ISBN 0-15-516765-0。
  • 詹姆斯和佩拉,罗科·j .(2007 年)。关于李克特量表和李克特反应格式及其解药的十大常见误解、误解、持续神话和都市传说社会科学杂志。3 (3): 106–116.DOI:10.3844/jssp.2007.106.116

二、EDA 的视觉辅助工具

作为数据科学家,我们工作的两个重要目标是从数据中提取知识,并将数据呈现给利益相关者。向涉众展示结果是非常复杂的,因为我们的受众可能没有足够的技术知识来理解编程术语和其他技术细节。因此,视觉辅助是非常有用的工具。在这一章中,我们将重点介绍可用于数据集的不同类型的视觉辅助工具。我们将学习可用于数据可视化的不同类型的技术。

在本章中,我们将涵盖以下主题:

  • 折线图
  • 条形图
  • 散点图
  • 面积图和堆叠图
  • 圆形分格统计图表
  • 表格图表
  • 极区图
  • 柱状图
  • 棒棒糖图表
  • 选择最佳图表
  • 要探索的其他库

技术要求

你可以在 GitHub 上找到这一章的代码:https://GitHub . com/PacktPublishing/动手-探索-数据-分析-用 python 。为了充分利用本章,请确保以下几点:

  • 确保你有 Python 3。安装在你的电脑上。建议使用 Python 等 Python 笔记本。
  • 您必须安装 Python 库,如pandasseabornmatplotlib

折线图

你还记得什么是连续变量,什么是离散变量吗?如果没有,快速看一下第一章探索性数据分析基本面。回到主题,折线图用于说明两个或多个连续变量之间的关系。

我们将使用matplotlib库和股价数据绘制时间序列线。首先,让我们了解数据集。我们已经使用faker Python 库创建了一个函数来生成数据集。这是你能想象到的最简单的数据集,只有两列。第一栏是Date,第二栏是Price,表示当日股价。

让我们通过调用 helper 方法来生成数据集。除此之外,我们还保存了 CSV 文件。您可以选择使用pandas ( read_csv)库加载 CSV 文件并继续可视化。

我的generateData功能在这里定义:

import datetime
import math
import pandas as pd
import random 
import radar 
from faker import Faker
fake = Faker()

def generateData(n):
  listdata = []
  start = datetime.datetime(2019, 8, 1)
  end = datetime.datetime(2019, 8, 30)
  delta = end - start
  for _ in range(n):
    date = radar.random_datetime(start='2019-08-1', stop='2019-08-30').strftime("%Y-%m-%d")
    price = round(random.uniform(900, 1000), 4)
    listdata.append([date, price])
  df = pd.DataFrame(listdata, columns = ['Date', 'Price'])
  df['Date'] = pd.to_datetime(df['Date'], format='%Y-%m-%d')
  df = df.groupby(by='Date').mean()

  return df

定义了生成数据的方法后,让我们将数据放入 pandas 数据框,并检查前 10 个条目:

df = generateData(50)
df.head(10)

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

让我们在下一节创建折线图。

涉及的步骤

让我们看看创建折线图的过程:

  1. 加载并准备数据集。我们将在第 4 章数据转换中详细了解如何准备数据。对于本练习,所有数据都经过预处理。
  2. 导入matplotlib库。这可以通过以下命令完成:
import matplotlib.pyplot as plt
  1. 绘制图表:
plt.plot(df)
  1. 在屏幕上显示:
plt.show()

如果我们把它们放在一起,代码如下:

import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = (14, 10)
plt.plot(df)

绘制的图表看起来像这样:

在前面的示例中,我们假设数据以 CSV 格式提供。在现实场景中,数据大多以 CSV、JSON、Excel 或 XML 格式提供,并且大多通过一些标准的应用编程接口传播。对于本系列,我们假设您已经熟悉 Pandas 以及如何读取不同类型的文件。如果没有,是时候修改 Pandas 了。更多详情请参考 Pandas 文档:https://pandas-datareader.readthedocs.io/en/latest/

条形图

这是几乎每个人都会遇到的最常见的可视化类型之一。可以水平或垂直绘制条来表示分类变量

条形图经常用于区分不同集合之间的对象,以便跟踪随时间的变化。在大多数情况下,当变化较大时,条形图非常方便。为了了解条形图,让我们假设挪威的一家药店记录了每月左洛复的销售量。左洛复是一种开给抑郁症患者的药。我们可以使用calendar Python 库来跟踪一年中对应于 1 月到 12 月的月份(1 到 12):

  1. 让我们导入所需的库:
import numpy as np
import calendar
import matplotlib.pyplot as plt
  1. 设置数据。请记住,range停止参数是排他的,这意味着如果您从最后一项13生成范围,则不包括:
months = list(range(1, 13))
sold_quantity = [round(random.uniform(100, 200)) for x in range(1, 13)]
  1. 指定图形的布局并分配空间:
figure, axis = plt.subplots()
  1. x 轴上,我们想要显示月份的名称:
plt.xticks(months, calendar.month_name[1:13], rotation=20)
  1. 绘制图表:
plot = axis.bar(months, sold_quantity)
  1. 此步骤是可选的,具体取决于您是否有兴趣在条形图头部显示数据值。它直观地显示了酒吧本身实际售出商品的数量,更有意义:
for rectangle in plot:
height = rectangle.get_height()
axis.text(rectangle.get_x() + rectangle.get_width() /2., 1.002 * height, '%d' % int(height), ha='center', va = 'bottom')
  1. 在屏幕上显示图形:
plt.show()

条形图如下所示:

以下是之前可视化的重要观察结果:

  • monthssold_quantity是 Python 列表,代表每个月左洛复的销量。
  • 我们使用的是前面代码中的subplots()方法。为什么呢?它提供了一种根据图形数量定义图形布局的方法,并提供了组织图形的方法。还困惑吗?别担心,我们会在这一章中多次使用支线剧情。此外,如果你需要快速参考,Packt 有几本书解释matplotlib。本章的进一步阅读部分提到了一些最有趣的阅读。
  • 步骤 3 中,我们使用plt.xticks()功能,该功能允许我们将 x 轴的刻度从 1 更改为 12,而calender.months[1:13]calendar Python 库中将该数值格式更改为相应的月份。
  • 第 4 步实际打印的是销售的月份和数量。
  • for循环中的ax.text()用相应的值标注每个条。它是如何做到这一点的可能很有趣。我们绘制这些值的方法是获取 x 和 y 坐标,然后将bar_width/2添加到高度为1.002的 x 坐标中,这是 y 坐标。然后,使用vaha参数,我们将文本居中对齐。
  • 第 6 步在屏幕上实际显示图形。

正如在本节的介绍中提到的,我们说过横条可以是水平的,也可以是垂直的。让我们换成水平格式。所有代码保持不变,除了plt.xticks更改为plt.yticks()plt.bar()更改为plt.barh()。我们假设它是不言自明的。

除此之外,放置精确的数据值有点棘手,需要多次反复试验才能完美放置它们。但是让我们看看他们的行动:

months = list(range(1, 13))
sold_quantity = [round(random.uniform(100, 200)) for x in range(1, 13)]

figure, axis = plt.subplots()

plt.yticks(months, calendar.month_name[1:13], rotation=20)

plot = axis.barh(months, sold_quantity)

for rectangle in plot:
  width = rectangle.get_width()
  axis.text(width + 2.5, rectangle.get_y() + 0.38, '%d' % int(width), ha='center', va = 'bottom')

plt.show()

它生成的图形如下:

好了,这就是本章中的条形图。在后面的章节中,我们肯定会用到其他几个属性。接下来,我们将使用散点图来可视化数据。

散点图

散点图也称为散点图、散点图、散点图和散点图。他们使用笛卡尔坐标系统来显示一组数据的两个变量的值。

我们什么时候应该使用散点图?散点图可以在以下两种情况下构建:

  • 当一个连续变量依赖于另一个受观察者控制的变量时
  • 当两个连续变量都独立时

有两个重要的概念——自变量因变量。在统计建模或数学建模中,因变量的值依赖于自变量的值。因变量是正在研究的结果变量。自变量也称为回归量。这里的要点是,当我们需要显示两个变量之间的关系时,使用散点图,因此有时称为相关图。我们将在第 7 章相关性中挖掘更多关于相关性的细节。

你要么是数据科学家专家,要么是计算机科学的初学者,毫无疑问你以前遇到过散点图的形式。这些图是可视化的强大工具,尽管它们很简单。主要原因是它们有很多选项、表示能力和设计选择,并且足够灵活,能够以吸引人的方式表示图形。

散点图适用的一些例子如下:

  • 研究已经成功地证实,一个人所需的睡眠时间取决于这个人的年龄。
  • 成年人的平均收入是基于受教育的年限。

我们来看第一个案例。数据集可以在 GitHub 存储库中以 CSV 文件的形式找到:

headers_cols = ['age','min_recommended', 'max_recommended', 'may_be_appropriate_min', 'may_be_appropriate_max', 'min_not_recommended', 'max_not_recommended'] 

sleepDf = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/hands-on-exploratory-data-analysis-with-python/master/Chapter%202/sleep_vs_age.csv', columns=headers_cols)
sleepDf.head(10)

正确导入数据集后,让我们显示散点图。我们首先导入所需的库,然后绘制实际的图形。接下来,我们显示 x 标签和 y 标签。代码在以下代码块中给出:

import seaborn as sns
import matplotlib.pyplot as plt
sns.set()

# A regular scatter plot
plt.scatter(x=sleepDf["age"]/12., y=sleepDf["min_recommended"])
plt.scatter(x=sleepDf["age"]/12., y=sleepDf['max_recommended'])
plt.xlabel('Age of person in Years')
plt.ylabel('Total hours of sleep required')
plt.show()

由上述代码生成的散点图如下:

这并不难,是吗?让我们看看能否解释这个图表。你可以清楚地看到,一个人最初需要的睡眠总时数很高,随着年龄的增长而逐渐减少。产生的图形是可解释的,但是由于缺少连续的线,结果不是不言自明的。让我们在上面画一条线,看看这是否能以更明显的方式解释结果:

# Line plot
plt.plot(sleepDf['age']/12., sleepDf['min_recommended'], 'g--')
plt.plot(sleepDf['age']/12., sleepDf['max_recommended'], 'r--')
plt.xlabel('Age of person in Years')
plt.ylabel('Total hours of sleep required')
plt.show()

相同数据的折线图如下:

从图中可以明显看出,这两条线随着年龄的增长而下降。它表明 0 到 3 个月的新生儿每天至少需要 14-17 个小时的睡眠。同时,成年人和老年人每天需要 7-9 个小时的睡眠。你的睡眠模式在这个范围内吗?

让我们再举一个散点图的例子,使用数据科学中最流行的数据集 Iris 数据集。数据集由罗纳德·费雪于 1936 年引入,并被博客、书籍、文章和研究论文广泛采用,以展示数据科学和数据挖掘的各个方面。该数据集包含三种不同鸢尾的 50 个例子,分别命名为濑户鸢尾、弗吉尼亚鸢尾和云芝。每个示例有四个不同的属性:petal_lengthpetal_widthsepal_lengthsepal_width。数据集可以通过几种方式加载。

这里,我们使用seaborn加载数据集:

  1. 导入seaborn并设置matplotlib的一些默认参数:
import seaborn as sns
import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = (8, 6)
plt.rcParams['figure.dpi'] = 150
  1. seaborn开始使用样式。试着评论下一行,看看图表中的不同之处:
sns.set()
  1. 加载虹膜数据集:
df = sns.load_dataset('iris')

df['species'] = df['species'].map({'setosa': 0, "versicolor": 1, "virginica": 2})
  1. 创建常规散点图:
plt.scatter(x=df["sepal_length"], y=df["sepal_width"], c = df.species)
  1. 为轴创建标签:
plt.xlabel('Septal Length')
plt.ylabel('Petal length')
  1. 在屏幕上显示绘图:
plt.show()

由上述代码生成的散点图如下:

你觉得这张图表信息丰富吗?我们假设你们大多数人都同意,你们可以清楚地看到三种不同类型的点,并且有三种不同的簇。然而,不清楚哪种颜色代表哪种鸢尾。因此,我们将学习如何在散点图中使用海底部分创建传说。

泡泡图

气泡图是散点图的一种表现形式,图中的每个数据点都显示为一个气泡。每个气泡可以用不同的颜色、大小和外观来说明。

让我们继续使用 Iris 数据集来获得一个气泡图。这里需要注意的重要一点是,我们还是要用plt.scatter的方法画一个泡泡图:

# Load the Iris dataset
df = sns.load_dataset('iris')

df['species'] = df['species'].map({'setosa': 0, "versicolor": 1, "virginica": 2})

# Create bubble plot
plt.scatter(df.petal_length, df.petal_width,
            s=50*df.petal_length*df.petal_width, 
            c=df.species,
            alpha=0.3
            )

# Create labels for axises
plt.xlabel('Septal Length')
plt.ylabel('Petal length')
plt.show()

由上述代码生成的气泡图如下:

你能解释结果吗?嗯,从图上看不清楚哪种颜色代表哪种鸢尾。但是我们可以清楚地看到三个不同的,这清楚地表明对于每个特定的物种或簇,花瓣长度和花瓣宽度之间存在关系。

使用 seaborn 的散点图

还可以使用seaborn库生成散点图。Seaborn 使图表在视觉上更好。我们可以利用seaborn散点图的sizestylehue参数来说明不同数据子集的 x 和 y 之间的关系。

Get more detailed information about the parameters from seaborn's documentation website: https://seaborn.pydata.org/generated/seaborn.scatterplot.html.

现在,让我们加载 Iris 数据集:

df = sns.load_dataset('iris')

df['species'] = df['species'].map({'setosa': 0, "versicolor": 1, "virginica": 2})
sns.scatterplot(x=df["sepal_length"], y=df["sepal_width"], hue=df.species, data=df)

根据前面的代码生成的散点图如下:

在前面的情节中,我们可以清楚地看到有三种花用三种不同的颜色表示。从图中可以更清楚地看到不同的花在萼片的宽度和长度上是如何变化的。

面积图和堆叠图

堆叠图之所以得名,是因为它代表了线形图下面的区域,而且几个这样的图可以堆叠在一起,给人一种堆叠的感觉。当我们想要可视化在 y 轴上绘制的多个变量的累积效应时,堆叠图会很有用。

为了简化这一点,可以将面积图想象成一个线形图,通过用颜色填充来显示所覆盖的区域。说够了。让我们深入代码库。首先,让我们定义数据集:

# House loan Mortgage cost per month for a year
houseLoanMortgage = [9000, 9000, 8000, 9000, 
                    8000, 9000, 9000, 9000, 
                    9000, 8000, 9000, 9000]

# Utilities Bills for a year
utilitiesBills = [4218, 4218, 4218, 4218,
                  4218, 4218, 4219, 2218, 
                  3218, 4233, 3000, 3000]
# Transportation bill for a year
transportation = [782, 900, 732, 892,
                  334, 222, 300, 800, 
                  900, 582, 596, 222]

# Car mortgage cost for one year
carMortgage = [700, 701, 702, 703, 
              704, 705, 706, 707, 
              708, 709, 710, 711]

现在,让我们导入所需的库并绘制堆叠图:

import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

months= [x for x in range(1,13)]

# Create placeholders for plot and add required color 
plt.plot([],[], color='sandybrown', label='houseLoanMortgage')
plt.plot([],[], color='tan', label='utilitiesBills')
plt.plot([],[], color='bisque', label='transportation')
plt.plot([],[], color='darkcyan', label='carMortgage')

# Add stacks to the plot
plt.stackplot(months, houseLoanMortgage, utilitiesBills, transportation, carMortgage, colors=['sandybrown', 'tan', 'bisque', 'darkcyan'])
plt.legend()

# Add Labels
plt.title('Household Expenses')
plt.xlabel('Months of the year')
plt.ylabel('Cost')

# Display on the screen
plt.show()

在前面的片段中,首先,我们导入了matplotlibseaborn。没什么新鲜的,对吧?然后我们添加了带有图例的堆栈。最后,我们在轴上添加标签,并在屏幕上显示图形。简单明了。现在您知道如何创建面积图或堆叠图了。由上述代码生成的面积图如下:

现在最重要的部分是解释图表的能力。在上图中,很明显,房屋抵押贷款是最大的支出,因为房屋抵押贷款的曲线下面积最大。其次,公用事业账单的面积占第二大面积,以此类推。图表清楚地向目标受众传播了有意义的信息。标签、图例和颜色是创建有意义的可视化的重要方面。

圆形分格统计图表

这是更有趣的数据可视化图形类型之一。我们说有趣不是因为它有更高的偏好或更强的说明能力,而是因为它是研究中最有争议的可视化类型之一。

伊恩·斯彭斯在 2005 年的一篇论文《不要卑微的馅饼:统计图的起源和使用》认为,馅饼图无法吸引大多数专家。尽管有类似的研究,人们仍然选择使用饼状图。社区给出了几个不遵守饼图的理由。其中一个论点是,人类天生不擅长一眼就能分辨出圆圈切片中的差异。另一个论点是,人们倾向于高估钝角的大小。同样,人们似乎低估了锐角的大小。

看了批评,我们也要有一些积极性。一个相反的论点是:如果饼状图不能沟通,为什么它会持续存在?主要原因是人们热爱圈子。此外,饼图的目的是传达比例,它被广泛接受。说够了;让我们使用口袋妖怪数据集绘制一个饼图。有两种方法可以加载数据:第一,直接从 GitHub URL 加载;或者您可以从 GitHub 下载数据集,并通过提供正确的路径从本地机器引用它。无论哪种情况,都可以使用pandas库中的read_csv方法。查看以下代码片段:

# Create URL to JSON file (alternatively this can be a filepath)
url = 'https://raw.githubusercontent.com/hmcuesta/PDA_Book/master/Chapter3/pokemonByType.csv'

# Load the first sheet of the JSON file into a data frame
pokemon = pd.read_csv(url, index_col='type')

pokemon

前面的代码片段应该如下所示显示数据帧:

接下来,我们尝试绘制饼图:

import matplotlib.pyplot as plt

plt.pie(pokemon['amount'], labels=pokemon.index, shadow=False, startangle=90, autopct='%1.1f%%',)
plt.axis('equal') 
plt.show()

我们应该从前面的代码中得到下面的饼图:

你知道可以直接使用pandas库创建饼图吗?检查以下一行:

pokemon.plot.pie(y="amount", figsize=(20, 10))

生成的饼图如下:

我们用一行代码生成了一个漂亮的带有图例的饼图。这就是为什么说 Python 是喜剧演员。你知道为什么吗?因为它有很多俏皮话。很真实,对吧?

表格图表

表格图表结合了条形图和表格。为了理解表格图表,让我们考虑以下数据集。考虑不同瓦特数的标准发光二极管灯泡。标准的飞利浦发光二极管灯泡可以是 4.5 瓦、6 瓦、7 瓦、8.5 瓦、9.5 瓦、13.5 瓦和 15 瓦。让我们假设有两个分类变量,即年份和瓦数,以及一个数字变量,即特定年份售出的单位数量。

现在,让我们声明变量来保存年份和可用瓦数数据。这可以如下面的代码片段所示完成:

# Years under consideration
years = ["2010", "2011", "2012", "2013", "2014"]

# Available watt
columns = ['4.5W', '6.0W', '7.0W','8.5W','9.5W','13.5W','15W']
unitsSold = [
             [65, 141, 88, 111, 104, 71, 99],
             [85, 142, 89, 112, 103, 73, 98],
             [75, 143, 90, 113, 89, 75, 93],
             [65, 144, 91, 114, 90, 77, 92],
             [55, 145, 92, 115, 88, 79, 93],
            ]

# Define the range and scale for the y axis
values = np.arange(0, 600, 100)

我们现在已经准备好了数据集。现在,让我们尝试使用以下代码块绘制一个表格图表:

colors = plt.cm.OrRd(np.linspace(0, 0.7, len(years)))
index = np.arange(len(columns)) + 0.3
bar_width = 0.7

y_offset = np.zeros(len(columns))
fig, ax = plt.subplots()

cell_text = []

n_rows = len(unitsSold)
for row in range(n_rows):
    plot = plt.bar(index, unitsSold[row], bar_width, bottom=y_offset, 
                   color=colors[row])
    y_offset = y_offset + unitsSold[row]
    cell_text.append(['%1.1f' % (x) for x in y_offset])
    i=0
# Each iteration of this for loop, labels each bar with corresponding value for the given year
    for rect in plot:
        height = rect.get_height()
        ax.text(rect.get_x() + rect.get_width()/2, y_offset[i],'%d' 
                % int(y_offset[i]), 
                ha='center', va='bottom')
        i = i+1 

最后,让我们将表格添加到图表的底部:

# Add a table to the bottom of the axes
the_table = plt.table(cellText=cell_text, rowLabels=years, 
                rowColours=colors, colLabels=columns, loc='bottom')
plt.ylabel("Units Sold")
plt.xticks([])
plt.title('Number of LED Bulb Sold/Year')
plt.show()

前面的代码片段生成了一个漂亮的表格,如下所示:

请看前面的表格。你认为它容易被解释吗?很清楚,对吧?例如,你可以看到,在 2014 年,4.5 瓦灯泡售出了 345 台。类似地,同样的信息可以从前面的图表中推断出来。

极区图

你还记得数学课上的极轴吗?嗯,极坐标图是绘制在极轴上的图表。它的坐标是角度和半径,与 x 和 y 坐标的笛卡尔系统相反。有时,它也被称为蜘蛛网情节。让我们看看如何绘制一个极坐标图的例子。

首先,让我们创建数据集:

  1. 假设你一学年有五门课程:
subjects = ["C programming", "Numerical methods", "Operating system", "DBMS", "Computer Networks"]
  1. 并且你计划在每一科取得以下成绩:
plannedGrade = [90, 95, 92, 68, 68, 90]
  1. 然而,在你的期末考试后,这些是你的成绩:
actualGrade = [75, 89, 89, 80, 80, 75]

现在数据集已经准备好了,让我们尝试创建一个极坐标图。第一个重要步骤是初始化蜘蛛图。这可以通过设置图形大小和极坐标投影来完成。现在应该清楚了。请注意,在前面的数据集中,等级列表包含一个额外的条目。这是因为它是一个环形图,我们需要将第一点和最后一点连接在一起,形成一个环形流。因此,我们从每个列表中复制第一个条目,并将其附加到列表中。在前面的数据中,条目 90 和 75 分别是列表的第一个条目。让我们看看每一步:

  1. 导入所需的库:
import numpy as np
import matplotlib.pyplot as plt
  1. 准备数据集并设置theta:
theta = np.linspace(0, 2 * np.pi, len(plannedGrade))
  1. 用图形尺寸和极坐标投影初始化绘图:
plt.figure(figsize = (10,6))
plt.subplot(polar=True)
  1. 让网格线与每个主题名称对齐:
(lines,labels) = plt.thetagrids(range(0,360, int(360/len(subjects))),
 (subjects))
  1. 使用plt.plot方法绘制图形并填充其下的区域:
plt.plot(theta, plannedGrade)
plt.fill(theta, plannedGrade, 'b', alpha=0.2)
  1. 现在,我们绘制实际成绩:
plt.plot(theta, actualGrade)
  1. 我们在剧情中加入了一个传奇和一个通俗易懂的标题:
plt.legend(labels=('Planned Grades','Actual Grades'),loc=1)
plt.title("Plan vs Actual grades by Subject")
  1. 最后,我们在屏幕上展示剧情:
plt.show()

生成的极坐标图如下图所示:

如前面的输出所示,按科目划分的计划成绩和实际成绩很容易区分。图例明确了哪一行表示计划成绩(截图中的蓝线),哪一行表示实际成绩(截图中的橙线)。这向目标受众清楚地表明了学生的预测成绩和实际成绩之间的差异。

柱状图

直方图用于描述任何连续变量的分布。这些类型的图在统计分析中非常受欢迎。

考虑以下用例。在开发人员职业培训课程中进行的一项调查有 100 名参与者。他们有几年的 Python 编程经验,范围从 0 到 20。

让我们导入所需的库并创建数据集:

import numpy as np
import matplotlib.pyplot as plt

#Create data set
yearsOfExperience = np.array([10, 16, 14, 5, 10, 11, 16, 14, 3, 14, 13, 19, 2, 5, 7, 3, 20,
       11, 11, 14, 2, 20, 15, 11, 1, 15, 15, 15, 2, 9, 18, 1, 17, 18,
       13, 9, 20, 13, 17, 13, 15, 17, 10, 2, 11, 8, 5, 19, 2, 4, 9,
       17, 16, 13, 18, 5, 7, 18, 15, 20, 2, 7, 0, 4, 14, 1, 14, 18,
        8, 11, 12, 2, 9, 7, 11, 2, 6, 15, 2, 14, 13, 4, 6, 15, 3,
        6, 10, 2, 11, 0, 18, 0, 13, 16, 18, 5, 14, 7, 14, 18])
yearsOfExperience

要绘制直方图,请执行以下步骤:

  1. 绘制团队经验分布图:
nbins = 20
n, bins, patches = plt.hist(yearsOfExperience, bins=nbins)
  1. 向轴和标题添加标签:
plt.xlabel("Years of experience with Python Programming")
plt.ylabel("Frequency")
plt.title("Distribution of Python programming experience in the vocational training session")
  1. 在平均体验的图表中画一条绿色垂直线:
plt.axvline(x=yearsOfExperience.mean(), linewidth=3, color = 'g') 
  1. 显示图表:
plt.show()

前面的代码生成了以下直方图:

好多了,对吧?现在,从图表中,我们可以说参与者的平均经验是 10 年左右。我们能改进图表以获得更好的可读性吗?我们试着画出yearsOfExperience中所有条目总和的百分比怎么样?除此之外,我们还可以使用这些数据的平均值和标准偏差绘制一个正态分布来查看分布模式。如果您不确定正态分布是什么,我们建议您查阅第 1 章探索性数据分析基础中的参考资料。简而言之,正态分布也被称为高斯分布。该术语表示关于平均值对称的概率分布,说明接近平均值的数据比远离平均值的数据更频繁。足够的理论;让我们开始练习吧。

为了绘制分布,我们可以在plot.hist函数中添加一个density=1参数。让我们检查一下代码。注意步骤1456有变化。代码的其余部分与前面的示例相同:

  1. 绘制团队经验分布图:
plt.figure(figsize = (10,6))

nbins = 20
n, bins, patches = plt.hist(yearsOfExperience, bins=nbins, density=1)
  1. 向轴和标题添加标签:
plt.xlabel("Years of experience with Python Programming")
plt.ylabel("Frequency")
plt.title("Distribution of Python programming experience in the vocational training session")
  1. 在平均体验的图表中画一条绿色垂直线:
plt.axvline(x=yearsOfExperience.mean(), linewidth=3, color = 'g') 
  1. 计算数据集的平均值和标准偏差:
mu = yearsOfExperience.mean()
sigma = yearsOfExperience.std()
  1. 为正态分布添加一条最佳拟合线:
y = ((1 / (np.sqrt(2 * np.pi) * sigma)) * np.exp(-0.5 * (1 / sigma * (bins - mu))**2))
  1. 绘制正态分布:
plt.plot(bins, y, '--')
  1. 显示图表:
plt.show()

生成的正态分布直方图如下:

前面的图清楚地说明了它不遵循正态分布。正态分布的最佳拟合曲线上下有许多竖线。也许你想知道在前面的代码中,我们从哪里得到计算步骤 6 的公式。这里涉及到一个小理论。当我们提到正态分布时,我们可以利用((1 / (np.sqrt(2 * np.pi) * sigma)) * np.exp(-0.5 * (1 / sigma * (bins - mu))**2))给出的高斯分布函数来计算概率密度函数。

棒棒糖图表

棒棒糖图表可以用来显示数据中的排名。它类似于有序条形图。

让我们考虑carDF数据集。它可以在第 2 章的 GitHub 存储库中找到。或者,可以直接从 GitHub 链接使用它,如以下代码中所述:

  1. 加载数据集:
#Read the dataset

carDF = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/hands-on-exploratory-data-analysis-with-python/master/Chapter%202/cardata.csv')
  1. 通过manufacturer对数据集进行分组。现在,如果没有意义,请记住以下代码片段按特定字段对条目进行分组(我们将在第 4 章 【数据转换】中详细介绍groupby功能):
#Group by manufacturer and take average mileage
processedDF = carDF[['cty','manufacturer']].groupby('manufacturer').apply(lambda x: x.mean())
  1. cty对值进行排序并重置index(再次,我们将在第 4 章 【数据转换】中进行排序以及如何重置索引):
#Sort the values by cty and reset index
processedDF.sort_values('cty', inplace=True)
processedDF.reset_index(inplace=True)
  1. 绘制图表:
#Plot the graph
fig, ax = plt.subplots(figsize=(16,10), dpi= 80)
ax.vlines(x=processedDF.index, ymin=0, ymax=processedDF.cty, color='firebrick', alpha=0.7, linewidth=2)
ax.scatter(x=processedDF.index, y=processedDF.cty, s=75, color='firebrick', alpha=0.7)
  1. 注释标题:
#Annotate Title
ax.set_title('Lollipop Chart for Highway Mileage using car dataset', fontdict={'size':22})
  1. 标注标签、xticksylims:
ax.set_ylabel('Miles Per Gallon')
ax.set_xticks(processedDF.index)
ax.set_xticklabels(processedDF.manufacturer.str.upper(), rotation=65, fontdict={'horizontalalignment': 'right', 'size':12})
ax.set_ylim(0, 30)
  1. 在图中写出实际平均值,并显示图:
#Write the values in the plot
for row in processedDF.itertuples():
    ax.text(row.Index, row.cty+.5, s=round(row.cty, 2), horizontalalignment= 'center', verticalalignment='bottom', fontsize=14)

#Display the plot on the screen
plt.show()

由前面的代码片段生成的棒棒糖图表如下:

看了前面的输出,你现在知道为什么叫棒棒糖图了吧?顶部的线条和圆圈很好地说明了不同类型的汽车及其相关的每加仑行驶里程。现在,数据更有意义了,不是吗?

选择最佳图表

没有标准定义您应该选择哪个图表来可视化您的数据。然而,有一些指导方针可以帮助你。以下是其中的一些:

  • 正如我们之前看到的每个图表所提到的,了解您拥有的数据类型非常重要。如果你有连续变量,那么直方图将是一个很好的选择。同样,如果你想显示排名,一个有序的条形图将是一个很好的选择。

  • 选择有效传达数据正确和相关含义的图表,而不实际扭曲事实。

  • 简单是最好的。画一张简单易懂的图表被认为比画复杂的图表更好,复杂的图表需要几份报告和文本才能理解。

  • 选择一个不会让观众信息过载的图表。我们的目的应该是清楚地说明抽象的信息。

话虽如此,让我们看看是否可以根据各种目的概括一些类别的图表。

下表根据用途显示了不同类型的图表:

| 目的 | 图表 |
| 显示相关性 | 散点图相关图成对图随着带状图抖动计数图边缘直方图带有最佳拟合线的散点图带圆圈的气泡图 |
| 显示偏差 | 对比图分叉杆发散文本发散点图带有标记的分叉棒棒糖图 |
| 显示分布 | 连续变量直方图分类变量直方图密度图分类图直方图密度曲线人口金字塔小提琴情节欢乐情节分布点图箱线图 |
| 显示作文 | 华夫饼图表圆形分格统计图表树图条形图 |
| 显示更改 | 时间序列图带波峰和波谷注释的时间序列自相关图互相关图多重时间序列使用次级 y 轴绘制不同比例堆叠面积图季节性地块日历热图未堆叠的面积图 |
| 显示组 | 系统树图聚类图安德鲁斯曲线平行坐标 |
| 显示排名 | 有序条形图棒棒糖图表点图坡度图哑铃图 |

请注意,浏览表中提到的每一种情节都超出了本书的范围。然而,我们试图在本章中涵盖其中的大部分内容。其中一些将在接下来的章节中使用;我们将以更符合上下文的方式和高级设置来使用这些图表。

要探索的其他库

到目前为止,我们已经看到了使用matplotlibseaborn的不同类型的 2D 和三维可视化技术。除了这些广泛使用的 Python 库,您还可以探索其他库:

  • Ploty(https://plot.ly/python/):这是一个基于网络应用的可视化工具包。它为 Jupyter Notebook 和其他应用程序提供的 API 使其非常强大,可以表示 2D 和 3D 图表。
  • Ggplot(http://ggplot.yhathq.com/):这是基于 R 编程语言的Graphics库的 Python 实现。
  • Altair(https://altair-viz.github.io/):这是建立在强大的 Vega-Lite 可视化语法之上,遵循非常声明性的统计可视化库技术。除此之外,它还有一个非常描述性和简单的 API。

摘要

一方面,以图形的方式描绘任何数据、事件、概念、信息、过程或方法总是被认为具有高度的理解力,另一方面,也很容易销售。向涉众展示结果是非常复杂的,因为我们的受众可能没有足够的技术来理解编程术语和技术细节。因此,视觉辅助工具被广泛使用。在本章中,我们讨论了如何使用这样的数据可视化工具。

在下一章中,我们将以一种非常简单的方式开始探索性数据分析。我们将尝试分析我们的邮箱,并分析我们发送和接收的电子邮件类型。

进一步阅读

  • Matplotlib 3.0 食谱斯里尼瓦萨·拉奥·波拉迪帕克特出版,2018 年 10 月 22 日
  • Matplotlib 标绘食谱亚历山大·德弗特T5帕克特出版,2014 年 3 月 26 日**
  • Python 数据可视化马里奥·德布勒蒂姆·格罗曼**帕克特出版,2019 年 2 月 28 日
  • 《没有卑微的馅饼:统计图的起源和使用》,伊恩·斯彭斯,多伦多大学,2005 年。

三、个人电子邮件 EDA

从数据集探索有用的见解需要大量的思考以及高水平的经验和实践。您处理不同类型的数据集越多,您在理解可以挖掘的见解类型方面获得的经验就越多。例如,如果您处理过文本数据集,您会发现您可以挖掘许多关键词、模式和短语。类似地,如果您处理过时间序列数据集,那么您将会理解您可以挖掘与周、月和季节相关的模式。这里的要点是,你练习得越多,你就越能理解可以提取的洞见类型和可以完成的可视化类型。话虽如此,在本章中,我们将使用我们自己的一个电子邮件数据集并执行探索性数据分析 ( EDA )。

您将了解如何将所有电子邮件导出为数据集,如何在 Pandas 数据框中使用导入,如何可视化它们,以及您可以获得的不同类型的见解。

在本章中,我们将涵盖以下主题:

  • 正在加载数据集
  • 数据转换
  • 数据分析
  • 进一步阅读建议

技术要求

本章的代码可以在Chapter 3文件夹中与本书共享的 GitHub 存储库中找到。这个数据集由我个人 Gmail 账户中的电子邮件数据组成。由于隐私问题,无法与您共享数据集。但是,在本章中,我们将指导您如何从 Gmail 下载自己的电子邮件来执行初始数据分析。

以下是要遵循的步骤:

  1. 登录您的个人 Gmail 帐户。
  2. 转到以下链接:https://takeout.google.com/settings/takeout
  3. 取消选择除 Gmail 以外的所有项目,如下图所示:

  1. 选择归档格式,如下图所示:

注意我选择了通过邮件发送下载链接一次性存档。拉链,最大允许尺寸。您可以自定义格式。完成后,点击创建档案

您将获得一个可供下载的电子邮件存档。您可以使用mbox文件的路径进行进一步分析,这将在本章中讨论。

现在让我们加载数据集。

正在加载数据集

首先,下载数据集至关重要。按照技术要求部分的上述步骤,下载数据。Gmail(https://takeout.google.com/settings/takeout)提供mbox格式的数据。对于这一章,我从谷歌邮件加载了我自己的个人电子邮件。出于隐私原因,我无法共享数据集。但是,我将向您展示您可以执行的不同 EDA 操作,以分析您的电子邮件行为的几个方面:

  1. 让我们加载所需的库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

Note that for this analysis, we need to have the mailbox package installed. If it is not installed on your system, it can be added to your Python build using the pip install mailbox instruction.

  1. 加载库后,加载数据集:
import mailbox

mboxfile = "PATH TO DOWNLOADED MBOX FIL"
mbox = mailbox.mbox(mboxfile)
mbox

请注意,用您自己的路径替换mbox文件路径是很重要的。

前面代码的输出如下:

<mailbox.mbox at 0x7f124763f5c0>

输出表明邮箱已成功创建。

  1. 接下来,让我们看看可用密钥的列表:
for key in mbox[0].keys():
  print(key)

前面代码的输出如下:

X-GM-THRID
X-Gmail-Labels
Delivered-To
Received
X-Google-Smtp-Source
X-Received
ARC-Seal
ARC-Message-Signature
ARC-Authentication-Results
Return-Path
Received
Received-SPF
Authentication-Results
DKIM-Signature
DKIM-Signature
Subject
From
To
Reply-To
Date
MIME-Version
Content-Type
X-Mailer
X-Complaints-To
X-Feedback-ID
List-Unsubscribe
Message-ID

前面的输出显示了提取的数据集中存在的键的列表。

数据转换

虽然提取的数据返回了很多对象,但是我们并不需要所有的项目。我们将只提取必填字段。数据清理是数据分析阶段的重要步骤之一。对于我们的分析,我们只需要以下数据:主题、开始、日期、结束、标签、线程

让我们在下面几节中看看数据转换中涉及的所有步骤。

数据清理

让我们创建一个只有必填字段的 CSV 文件。让我们从以下步骤开始:

  1. 导入csv包:
import csv
  1. 创建仅包含所需属性的 CSV 文件:
with open('mailbox.csv', 'w') as outputfile:
 writer = csv.writer(outputfile)
  writer.writerow(['subject','from','date','to','label','thread'])

  for message in mbox:
    writer.writerow([
      message['subject'], 
      message['from'], 
      message['date'], 
      message['to'], 
      message['X-Gmail-Labels'], 
      message['X-GM-THRID']
    ]
  )

前面的输出是一个名为mailbox.csvcsv文件。接下来,我们可以使用比原始数据集更小的 CSV 文件进行加载,而不是加载 mbox 文件。

正在加载 CSV 文件

我们将加载 CSV 文件。请参考以下代码块:

dfs = pd.read_csv('mailbox.csv', names=['subject', 'from', 'date', 'to', 'label', 'thread'])

前面的代码将生成一个 pandas 数据帧,其中只有必填字段存储在 CSV 文件中。

转换日期

接下来,我们将转换日期。

检查每列的数据类型,如下所示:

dfs.dtypes

前面代码的输出如下:

subject object
from object
date object
to object
label object
thread float64
dtype: object

注意,日期字段是一个对象。因此,我们需要将其转换为 DateTime 参数。在下一步中,我们将把日期字段转换成实际的日期时间参数。我们可以通过 Pandasto_datetime()方法做到这一点。请参见以下代码:

dfs['date'] = dfs['date'].apply(lambda x: pd.to_datetime(x, errors='coerce', utc=True))

让我们进入下一步,即从字段中删除 NaN 值。

正在删除 NaN 值

接下来,我们将从字段中删除 NaN 值。

我们可以这样做:

dfs = dfs[dfs['date'].notna()]

接下来,最好将预处理文件保存到一个单独的 CSV 文件中,以防我们再次需要它。我们可以将数据帧保存到一个单独的 CSV 文件中,如下所示:

dfs.to_csv('gmail.csv')

太好了。完成后,让我们做一些描述性的统计。

应用描述性统计

预处理数据集后,让我们使用描述性统计技术进行一些健全性检查。

我们可以实现如下所示:

dfs.info()

前面代码的输出如下:

<class 'pandas.core.frame.DataFrame'>
Int64Index: 37554 entries, 1 to 78442
Data columns (total 6 columns):
subject 37367 non-null object
from 37554 non-null object
date 37554 non-null datetime64[ns, UTC]
to 36882 non-null object
label 36962 non-null object
thread 37554 non-null object
dtypes: datetime64[ns, UTC](1), object(5)
memory usage: 2.0+ MB

我们将在第 5 章描述性统计中了解更多关于描述性统计的信息。请注意,共有 37,554 封电子邮件,每封电子邮件包含六列—主题、开始日期、结束日期、标签和主题。让我们检查电子邮件数据集的前几个条目:

dfs.head(10)

前面代码的输出如下:

请注意,到目前为止,我们的数据框包含六个不同的列。看一下from字段:它包含姓名和电子邮件。对于我们的分析,我们只需要一个电子邮件地址。我们可以使用正则表达式重构列。

数据重构

我们注意到from字段包含的信息比我们需要的要多。我们只需要从那个字段中提取一个电子邮件地址。让我们做一些重构:

  1. 首先,导入正则表达式包:
import re
  1. 接下来,让我们创建一个函数,从任何一列中提取整个字符串并提取一个电子邮件地址:
def extract_email_ID(string):
  email = re.findall(r'<(.+?)>', string)
  if not email:
    email = list(filter(lambda y: '@' in y, string.split()))
  return email[0] if email else np.nan

前面的函数很简单,对吧?我们使用正则表达式来查找电子邮件地址。如果没有电子邮件地址,我们用NaN填充该字段。好吧,如果你不确定正则表达式,别担心。只需阅读附录

  1. 接下来,让我们将该函数应用于from列:
dfs['from'] = dfs['from'].apply(lambda x: extract_email_ID(x))

我们使用 lambda 函数将该函数应用于列中的每个值。

  1. 接下来,我们将重构label字段。逻辑很简单。如果一封电子邮件来自你的电子邮件地址,那么它就是发送的电子邮件。否则,它是收到的电子邮件,即收件箱电子邮件:
myemail = 'itsmeskm99@gmail.com'
dfs['label'] = dfs['from'].apply(lambda x: 'sent' if x==myemail else 'inbox')

前面的代码是不言自明的。

删除列

让我们删除一列:

  1. 注意to栏只包含你自己的邮件。所以,我们可以去掉这个无关紧要的栏目:
dfs.drop(columns='to', inplace=True)
  1. 这将从数据框中删除to列。现在让我们显示前 10 个条目:
dfs.head(10)

前面代码的输出如下:

检查前面的输出。田地被打扫了。数据被转换成正确的格式。

重构时区

接下来,我们希望基于我们的时区重构时区:

  1. 我们可以使用这里给出的方法重构时区:
import datetime 
import pytz

def refactor_timezone(x):
  est = pytz.timezone('US/Eastern')
  return x.astimezone(est)

请注意,在前面的代码中,我将时区转换为美国/东方时区。你可以选择你喜欢的时区。

  1. 现在我们的函数已经创建,让我们称之为:
dfs['date'] = dfs['date'].apply(lambda x: refactor_timezone(x))
  1. 接下来,我们要将星期几变量转换为日的名称,如、SaturdaySunday等。我们可以这样做,如下所示:
dfs['dayofweek'] = dfs['date'].apply(lambda x: x.weekday_name)
dfs['dayofweek'] = pd.Categorical(dfs['dayofweek'], categories=[
    'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
    'Saturday', 'Sunday'], ordered=True)
  1. 太好了。接下来,我们在一天中的这个时间做同样的过程。请参见这里给出的片段:
dfs['timeofday'] = dfs['date'].apply(lambda x: x.hour + x.minute/60 + x.second/3600)
  1. 接下来,我们分别重构小时、年整数和年分数。首先,重构小时,如下所示:
dfs['hour'] = dfs['date'].apply(lambda x: x.hour)
  1. 重构年整数,如下所示:
dfs['year_int'] = dfs['date'].apply(lambda x: x.year)
  1. 最后,重构年分数,如下所示:
dfs['year'] = dfs['date'].apply(lambda x: x.year + x.dayofyear/365.25)
  1. 完成后,我们可以将日期设置为index,并且不再需要原始的date字段。所以,我们可以去掉它:
dfs.index = dfs['date']
del dfs['date']

太好了。到目前为止干得不错。我们已经成功执行了数据转换步骤。如果有些步骤不清楚,不要担心——我们将在接下来的章节中详细讨论这些阶段。

数据分析

这是 EDA 最重要的部分。这是我们从现有数据中获得见解的部分。

让我们逐一回答以下问题:

  1. 在给定的时间范围内,我发送了多少封电子邮件?
  2. 我在一天的什么时候用 Gmail 收发电子邮件?
  3. 平均每天的电子邮件数量是多少?
  4. 平均每小时的电子邮件数量是多少?
  5. 我和谁交流最频繁?
  6. 最活跃的电邮日是哪几天?
  7. 我发邮件主要是为了什么?

在接下来的部分中,我们将回答前面的问题。

电子邮件数量

第一个问题的答案是,“在给定的时间范围内,我发送了多少封电子邮件?”,可以这样回答:

print(dfs.index.min().strftime('%a, %d %b %Y %I:%M %p'))
print(dfs.index.max().strftime('%a, %d %b %Y %I:%M %p'))

print(dfs['label'].value_counts())

这里给出了前面代码的输出:

Tue, 24 May 2011 11:04 AM
Fri, 20 Sep 2019 03:04 PM
inbox 32952
sent 4602
Name: label, dtype: int64

如果您分析输出,您会看到我们分析了从 2011 年 5 月 24 日星期二上午 11:04 到 2019 年 9 月 20 日下午 03:04 Fri 的电子邮件。在此期间,共收到 32,952 封电子邮件,发送了 4,602 封电子邮件。这是一个很好的见解,对不对?现在,让我们跳到下一个问题。

一天中的时间

要回答下一个问题,我在一天的什么时间用 Gmail 收发邮件?我们来创建一个图表。我们将查看发送的电子邮件和接收的电子邮件:

  1. 让我们创建两个子数据框—一个用于发送的电子邮件,另一个用于接收的电子邮件:
sent = dfs[dfs['label']=='sent']
received = dfs[dfs['label']=='inbox']

很明显,对吧?记住,我们早些时候设置了几个标签sentinbox。现在,让我们创建一个情节。

  1. 首先,让我们导入所需的库:
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
from scipy import ndimage
import matplotlib.gridspec as gridspec
import matplotlib.patches as mpatches
  1. 现在,让我们创建一个函数,将数据帧作为输入并创建一个图。请参见以下功能:
def plot_todo_vs_year(df, ax, color='C0', s=0.5, title=''):
  ind = np.zeros(len(df), dtype='bool')
  est = pytz.timezone('US/Eastern')

  df[~ind].plot.scatter('year', 'timeofday', s=s, alpha=0.6, ax=ax, color=color)
  ax.set_ylim(0, 24)
  ax.yaxis.set_major_locator(MaxNLocator(8))
  ax.set_yticklabels([datetime.datetime.strptime(str(int(np.mod(ts, 24))), "%H").strftime("%I %p") for ts in ax.get_yticks()]);

  ax.set_xlabel('')
  ax.set_ylabel('')
  ax.set_title(title)
  ax.grid(ls=':', color='k')

  return ax

现在,您应该已经熟悉了如何创建散点图。我们在第 2 章EDA 视觉辅助中详细讨论了这样做。如果你对某些术语感到困惑,建议你重温那一章。

  1. 现在,让我们绘制收到和发送的电子邮件。查看这里给出的代码:
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(15, 4))

plot_todo_vs_year(sent, ax[0], title='Sent')
plot_todo_vs_year(received, ax[1], title='Received')

前面代码的输出如下:

查看前面的图表。图形数据点的密度越高,电子邮件的数量就越多。请注意,发送的电子邮件数量少于接收的电子邮件数量。从 2018 年到 2020 年,我收到的电子邮件比我发送的还要多。请注意,我在下午 03:00 到上午 09:00 之间收到了大多数电子邮件。这个图表给出了一天中电子邮件活动时间的一个很好的概述。这回答了第二个问题。

每天和每小时的平均电子邮件数

让我们回答其余的问题,看看每天和每小时的平均电子邮件数量:

  1. 为此,我们将创建两个函数,一个计算每天的电子邮件总数,另一个绘制每小时的平均电子邮件数:
def plot_number_perday_per_year(df, ax, label=None, dt=0.3, **plot_kwargs):
    year = df[df['year'].notna()]['year'].values
    T = year.max() - year.min()
    bins = int(T / dt)
    weights = 1 / (np.ones_like(year) * dt * 365.25)
    ax.hist(year, bins=bins, weights=weights, label=label, **plot_kwargs);
    ax.grid(ls=':', color='k')

上面的代码创建了一个绘制平均每天电子邮件数量的函数。同样,让我们创建一个绘制每小时平均电子邮件数量的函数:

def plot_number_perdhour_per_year(df, ax, label=None, dt=1, smooth=False,
                      weight_fun=None, **plot_kwargs):

    tod = df[df['timeofday'].notna()]['timeofday'].values
    year = df[df['year'].notna()]['year'].values
    Ty = year.max() - year.min()
    T = tod.max() - tod.min()
    bins = int(T / dt)
    if weight_fun is None:
        weights = 1 / (np.ones_like(tod) * Ty * 365.25 / dt)
    else:
        weights = weight_fun(df)
    if smooth:
        hst, xedges = np.histogram(tod, bins=bins, weights=weights);
        x = np.delete(xedges, -1) + 0.5*(xedges[1] - xedges[0])
        hst = ndimage.gaussian_filter(hst, sigma=0.75)
        f = interp1d(x, hst, kind='cubic')
        x = np.linspace(x.min(), x.max(), 10000)
        hst = f(x)
        ax.plot(x, hst, label=label, **plot_kwargs)
    else:
        ax.hist(tod, bins=bins, weights=weights, label=label, **plot_kwargs);

    ax.grid(ls=':', color='k')
    orientation = plot_kwargs.get('orientation')
    if orientation is None or orientation == 'vertical':
        ax.set_xlim(0, 24)
        ax.xaxis.set_major_locator(MaxNLocator(8))
        ax.set_xticklabels([datetime.datetime.strptime(str(int(np.mod(ts, 24))), "%H").strftime("%I %p")
                            for ts in ax.get_xticks()]);
    elif orientation == 'horizontal':
        ax.set_ylim(0, 24)
        ax.yaxis.set_major_locator(MaxNLocator(8))
        ax.set_yticklabels([datetime.datetime.strptime(str(int(np.mod(ts, 24))), "%H").strftime("%I %p")
                            for ts in ax.get_yticks()]);

现在,让我们创建一个类来绘制给定时间范围内所有电子邮件的时间与年份的关系:

class TriplePlot:
  def __init__(self):
    gs = gridspec.GridSpec(6, 6)
    self.ax1 = plt.subplot(gs[2:6, :4])
    self.ax2 = plt.subplot(gs[2:6, 4:6], sharey=self.ax1)
    plt.setp(self.ax2.get_yticklabels(), visible=False);
    self.ax3 = plt.subplot(gs[:2, :4]) 
    plt.setp(self.ax3.get_xticklabels(), visible=False);

  def plot(self, df, color='darkblue', alpha=0.8, markersize=0.5, yr_bin=0.1, hr_bin=0.5):
    plot_todo_vs_year(df, self.ax1, color=color, s=markersize)
    plot_number_perdhour_per_year(df, self.ax2, dt=hr_bin, color=color, alpha=alpha, orientation='horizontal')
    self.ax2.set_xlabel('Average emails per hour')
    plot_number_perday_per_year(df, self.ax3, dt=yr_bin, color=color, alpha=alpha)
    self.ax3.set_ylabel('Average emails per day')

现在,最后,让我们实例化该类来绘制图表:

import matplotlib.gridspec as gridspec
import matplotlib.patches as mpatches

plt.figure(figsize=(12,12));
tpl = TriplePlot()

tpl.plot(received, color='C0', alpha=0.5)
tpl.plot(sent, color='C1', alpha=0.5)
p1 = mpatches.Patch(color='C0', label='Incoming', alpha=0.5)
p2 = mpatches.Patch(color='C1', label='Outgoing', alpha=0.5)
plt.legend(handles=[p1, p2], bbox_to_anchor=[1.45, 0.7], fontsize=14, shadow=True);

前面代码的输出如下:

上图显示了每小时和每个图表的平均电子邮件数。就我而言,大多数电子邮件交流发生在 2018 年至 2020 年之间。

每天的电子邮件数量

让我们从电子邮件中找出一周中最忙的一天:

counts = dfs.dayofweek.value_counts(sort=False)
counts.plot(kind='bar')

前面代码的输出如下:

前面的输出显示我最忙的一天是星期四。我在星期四收到大部分电子邮件。让我们更进一步,分别看看接收和发送电子邮件最活跃的日子:

sdw = sent.groupby('dayofweek').size() / len(sent)
rdw = received.groupby('dayofweek').size() / len(received)

df_tmp = pd.DataFrame(data={'Outgoing Email': sdw, 'Incoming Email':rdw})
df_tmp.plot(kind='bar', rot=45, figsize=(8,5), alpha=0.5)
plt.xlabel('');
plt.ylabel('Fraction of weekly emails');
plt.grid(ls=':', color='k', alpha=0.5)

前面代码的输出如下:

截图中显示的输出相当不错,对吧?现在,任何人都可以很容易地理解,我最活跃的电子邮件交流日是周四接收电子邮件,周一发送电子邮件。有道理。我通常周末不工作,所以,在周一,我总是在开始一天之前回复我的电子邮件。分析显示,这就是为什么周一我会有更多的外发邮件。

我们甚至可以更进一步。让我们找到一天中最活跃的电子邮件交流时间。我们很容易做到。请参见以下代码:

import scipy.ndimage
from scipy.interpolate import interp1d

plt.figure(figsize=(8,5))
ax = plt.subplot(111)
for ct, dow in enumerate(dfs.dayofweek.cat.categories):
    df_r = received[received['dayofweek']==dow]
    weights = np.ones(len(df_r)) / len(received)
    wfun = lambda x: weights
    plot_number_perdhour_per_year(df_r, ax, dt=1, smooth=True, color=f'C{ct}',
                      alpha=0.8, lw=3, label=dow, weight_fun=wfun)

    df_s = sent[sent['dayofweek']==dow]
    weights = np.ones(len(df_s)) / len(sent)
    wfun = lambda x: weights
    plot_number_perdhour_per_year(df_s, ax, dt=1, smooth=True, color=f'C{ct}',
                      alpha=0.8, lw=2, label=dow, ls='--', weight_fun=wfun)
ax.set_ylabel('Fraction of weekly emails per hour')
plt.legend(loc='upper left')

前面代码的输出如下:

太好了。这个图表有点复杂,但仍然很直观。从前面的图表中,我们注意到我最活跃的日子是星期一(发送电子邮件)和星期四(接收电子邮件)。此图显示,在星期一,我的活动持续时间在上午 09:00 到下午 12:00 之间。在星期四,我的活跃时间也在上午 9:00 到下午 12:00 之间。根据你的图表,你最活跃的时间是什么?

If you encounter any error, please check the number of sent emails and the number of received emails. The number of emails, in either case, should be greater than one. If you have less or equal to one email, in either case, make sure you comment out the appropriate line to remove the error.

最常用的词

关于你的电子邮件,最容易分析的事情之一是最常用的单词。我们可以创建一个单词云来查看最常用的单词。让我们首先删除存档的电子邮件:

from wordcloud import WordCloud 

df_no_arxiv = dfs[dfs['from'] != 'no-reply@arXiv.org']
text = ' '.join(map(str, sent['subject'].values))

接下来,我们来绘制单词云:

stopwords = ['Re', 'Fwd', '3A_']
wrd = WordCloud(width=700, height=480, margin=0, collocations=False)
for sw in stopwords:
    wrd.stopwords.add(sw)
wordcloud = wrd.generate(text)

plt.figure(figsize=(25,15))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.margins(x=0, y=0)

我添加了一些额外的停止词来从图表中过滤掉。我的输出如下:

这告诉我我最常交流的是什么。从 2011 年到 2019 年的邮件分析来看,最常用的词是新词、网站、项目、数据、WordPress 和网站。这真的很好,对吗?本章介绍的只是一个起点。你可以在其他几个方向上更进一步。

摘要

在本章中,我们以 mbox 格式从我们自己的 Gmail 帐户导入数据。然后,我们加载数据集并执行一些原始的 EDA 技术,包括数据加载、数据转换和数据分析。我们还试图回答一些关于电子邮件交流的基本问题。

在下一章中,我们将讨论数据转换。数据转换是数据分析最重要的步骤之一,因为数据越定性,结果越好。

进一步阅读

  • Pandas 食谱:科学计算食谱使用 Python 进行时间序列分析和数据可视化第一版,作者: Theodore Petrou,Packt 出版社,2017
  • 掌握 Pandas——第二版,作者:阿希什·库马尔,帕克特出版社,2019 年 10 月 25 日
  • 学习 Pandas——第二版,作者:迈克尔·海德特,帕克特出版社,2017 年 6 月 29 日

四、数据转换

探索性数据分析 ( EDA )的一个基本步骤就是数据角力。在本章中,我们将学习如何合并数据库样式的数据帧,在索引上合并,沿着轴连接,将数据与重叠相结合,使用分层索引进行整形,以及将长格式旋转到宽格式。我们将开始了解在传输我们的信息进行进一步检查之前必须完成的工作,包括删除重复项、替换值、重命名轴索引、离散化和宁滨以及检测和过滤异常值。我们将致力于使用函数、映射、排列和随机采样来转换数据,并计算指标/虚拟变量。

本章将涵盖以下主题:

  • 背景
  • 合并数据库样式的数据帧
  • 转化技术
  • 数据转换的好处

技术要求

本章的代码可以在Chapter 4内的 GitHub repo 中找到。

我们将使用以下 Python 库:

  • Pandas
  • NumPy
  • 希伯恩
  • Matplotlib

背景

数据转换是一组用于将数据从一种格式或结构转换为另一种格式或结构的技术。以下是一些转型活动的示例:

  • 重复数据删除涉及重复数据的识别和删除。
  • 键重组包括将任何具有内置含义的键转换为通用键。
  • 数据清理涉及从源语言中提取单词,删除过时、不准确、不完整的信息,而不提取意义或信息,以提高源数据的准确性。
  • 数据验证是制定规则或算法的过程,有助于针对一些已知问题验证不同类型的数据。
  • 格式修改包括从一种格式转换到另一种格式。
  • 数据推导包括创建一组规则,以从数据源生成更多信息。
  • 数据聚合涉及在不同类型的报告系统中搜索、提取、汇总和保存重要信息。
  • 数据集成涉及转换不同的数据类型,并将它们合并成一个通用的结构或模式。
  • 数据过滤包括识别与任何特定用户相关的信息。
  • 数据连接包括在两个或多个表之间建立关系。

转换数据的主要原因是为了获得更好的表示,以便转换后的数据与其他数据兼容。除此之外,系统中的互操作性可以通过遵循通用的数据结构和格式来实现。

说到这里,让我们在下一节开始研究数据集成的数据转换技术。

合并数据库样式的数据帧

许多初级开发人员在使用 Pandas 数据框时会感到困惑,尤其是关于何时使用appendconcatmergejoin。在本节中,我们将检查其中每一个的单独用例。

假设你在一所大学做教授,教一门软件工程课程和一门机器学习入门课程,有足够的学生分成两个班。每个班级的考试在两个独立的建筑中进行,由两个不同的教授评分。他们给了你两个不同的数据帧。在第一个例子中,让我们只考虑一个主题——软件工程课程。

查看以下截图:

在前面的数据集中,第一列包含关于学生标识符的信息,第二列包含他们各自在任何科目中的分数。在这两种情况下,数据帧的结构是相同的。在这种情况下,我们需要将它们连接起来。

我们可以通过 Pandasconcat()方法做到这一点:

dataframe = pd.concat([dataFrame1, dataFrame2], ignore_index=True)
dataframe

前面代码的输出是组合两个表的单个数据帧。这些表将被合并成一个单独的表,如下图所示:

ignore_index参数创建一个新的索引;如果没有它,我们会保留原始指数。请注意,我们沿着axis=0组合数据帧,也就是说,我们在同一个方向上将它们组合在一起。如果我们想把它们并排组合在一起呢?然后我们必须指定axis=1

使用以下代码查看差异:

pd.concat([dataFrame1, dataFrame2], axis=1)

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

注意输出的差异。当我们指定axis=1时,连接是并排进行的。

让我们继续使用我们在前面代码中讨论的相同案例。在第一个示例中,您收到了同一主题的两个数据帧文件。现在,考虑另一个使用案例,您正在教授两门课程:软件工程机器学习入门。您将从每个主题中获得两个数据帧:

  • 两个用于软件工程课程
  • 机器学习入门课程还有两门

检查以下数据帧:

如果您错过了,您需要在前面的数据帧中注意一些重要的细节:

  • 有一些学生没有参加软件工程考试。
  • 有一些学生没有参加机器学习考试。
  • 这两门课都有学生出现。

现在,假设你的部门主管走到你的办公桌前,开始用一系列问题来轰炸你:

  • 总共有多少学生参加了考试?
  • 有多少学生只出现在软件工程课程?
  • 有多少学生只为了机器学习课程而出现?

有几种方法可以回答这些问题。使用 EDA 技术就是其中之一。在本节中,我们将使用pandas库来回答前面的问题。

让我们检查两个主题的数据帧:

import pandas as pd

df1SE = pd.DataFrame({ 'StudentID': [9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29], 'ScoreSE' : [22, 66, 31, 51, 71, 91, 56, 32, 52, 73, 92]})
df2SE = pd.DataFrame({'StudentID': [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30], 'ScoreSE': [98, 93, 44, 77, 69, 56, 31, 53, 78, 93, 56, 77, 33, 56, 27]})

df1ML = pd.DataFrame({ 'StudentID': [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29], 'ScoreML' : [39, 49, 55, 77, 52, 86, 41, 77, 73, 51, 86, 82, 92, 23, 49]})
df2ML = pd.DataFrame({'StudentID': [2, 4, 6, 8, 10, 12, 14, 16, 18, 20], 'ScoreML': [93, 44, 78, 97, 87, 89, 39, 43, 88, 78]})`

如您在前面的数据集中所见,每个主题有两个数据框。所以,第一个任务是将这两个主题连接成一个。其次,这些学生参加了机器学习入门课程以及软件工程课程。因此,我们需要将这些分数合并到相同的数据帧中。有几种方法可以做到这一点。让我们探索一些选择。

沿着轴连接

这是第一种选择。我们将使用pandas库中的pd.concat()方法。

合并数据帧的代码如下:

# Option 1
dfSE = pd.concat([df1SE, df2SE], ignore_index=True)
dfML = pd.concat([df1ML, df2ML], ignore_index=True)

df = pd.concat([dfML, dfSE], axis=1)
df

代码现在应该是不言自明的了。我们首先将软件工程课程和机器学习课程的数据框串联起来。然后,我们将数据帧与axis=1连接起来,将它们并排放置。

前面代码的输出如下:

你可能注意到StudentID字段被重复了。之后可以做的一个方法是删除重复的字段。然而,让我们看看其他的选择。

使用带有内部联接的 df.merge

这是第二种选择。现在我们使用pandas库中的df.merge()方法。想法很简单。首先,我们连接来自每个对象的单个数据帧,然后我们使用df.merge()方法。

检查以下代码:

dfSE = pd.concat([df1SE, df2SE], ignore_index=True)
dfML = pd.concat([df1ML, df2ML], ignore_index=True)

df = dfSE.merge(dfML, how='inner')
df

在这里,您对每个数据帧执行了内部连接。也就是说,如果一个项目存在于两个数据帧中,它将包含在新的数据帧中。这意味着我们将获得一份出现在这两门课程中的学生名单。

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

请注意,这应该回答了前面提到的一个问题:我们现在知道有 21 名学生选修了这两门课程。

使用带有左连接的 pd.merge()方法

第三种选择是使用pd.merge()方法和左连接技术。现在,您应该已经理解了合并的概念。pd.merge()方法的参数允许我们使用不同类型的连接。

以下是连接的类型:

  • inner连接从两个或多个数据帧中获取交集。对应结构化查询语言 ( SQL )中的INNER JOIN
  • outer连接从两个或多个数据帧中获取联合。对应于 SQL 中的FULL OUTER JOIN
  • left连接仅使用左侧数据框中的键。对应于 SQL 中的LEFT OUTER JOIN
  • right连接仅使用右侧数据框中的键。对应于 SQL 中的RIGHT OUTER JOIN

让我们看看如何使用左外连接:

dfSE = pd.concat([df1SE, df2SE], ignore_index=True)
dfML = pd.concat([df1ML, df2ML], ignore_index=True)

df = dfSE.merge(dfML, how='left')
df

前面代码的输出如下:

如果你看了前面的截图,你可以正确回答有多少学生只出现在软件工程课程中。总数将是 26。请注意,这些学生没有参加机器学习考试,因此他们的分数被标记为NaN

使用带右连接的 pd.merge()方法

这是第四种选择。与我们已经看到的选项类似,我们可以使用right连接来获得出现在机器学习课程中的所有学生的列表。

这样做的代码如下:

dfSE = pd.concat([df1SE, df2SE], ignore_index=True)
dfML = pd.concat([df1ML, df2ML], ignore_index=True)

df = dfSE.merge(dfML, how='right')
df

这个片段的输出作为练习的一部分留给您来完成。检查哪些列具有NaN值。

将 pd.merge()方法用于外部联接

这是第五种选择。最后,我们想知道至少有一门课的学生总数。这可以通过使用outer连接来完成:

dfSE = pd.concat([df1SE, df2SE], ignore_index=True)
dfML = pd.concat([df1ML, df2ML], ignore_index=True)

df = dfSE.merge(dfML, how='outer')
df

检查输出,并将差异与之前的输出进行比较。

索引合并

有时,合并数据帧的键位于数据帧索引中。在这种情况下,我们可以通过left_index=Trueright_index=True来指示应该接受索引作为合并键。

索引合并按以下步骤完成:

  1. 考虑以下两个数据帧:
left1 = pd.DataFrame({'key': ['apple','ball','apple', 'apple', 'ball', 'cat'], 'value': range(6)})
right1 = pd.DataFrame({'group_val': [33.4, 5]}, index=['apple', 'ball'])

如果打印这两个数据帧,输出如下图所示:

请注意,第一个数据框中的键是 apple 、ball 和 cat。在第二个数据框中,我们有 apple 和 ball 键的组值。

  1. 现在,让我们考虑两种不同的情况。首先,让我们尝试使用内部连接进行合并,这是默认的合并类型。在这种情况下,默认合并是键的交集。检查以下示例代码:
df = pd.merge(left1, right1, left_on='key', right_index=True)
df

前面代码的输出如下:

输出是来自这些数据帧的键的交集。由于第二个数据帧中没有 cat 关键字,因此它不包含在最终的表中。

  1. 其次,让我们尝试使用outer连接进行合并,如下所示:
df = pd.merge(left1, right1, left_on='key', right_index=True, how='outer')
df

前面代码的输出如下:

注意最后一行包括键。这是因为outer的加入。

整形和旋转

在 EDA 过程中,我们经常需要以某种一致的方式重新排列数据框架中的数据。这可以通过使用两个动作的分级索引来完成:

  • 堆叠 : Stack从数据中的任何特定列旋转到行。
  • 拆垛:从行旋转到列。

我们将看下面的例子:

  1. 让我们创建一个数据框架,记录挪威五个不同县的降雨量、湿度和风力情况:
data = np.arange(15).reshape((3,5))
indexers = ['Rainfall', 'Humidity', 'Wind']
dframe1 = pd.DataFrame(data, index=indexers, columns=['Bergen', 'Oslo', 'Trondheim', 'Stavanger', 'Kristiansand'])
dframe1

前面片段的输出如下:

  1. 现在,使用前面的dframe1上的stack()方法,我们可以将列旋转成行以产生一个系列:
stacked = dframe1.stack()
stacked

这种堆叠的输出如下:

  1. 可以使用unstack()方法将变量中存储的前一系列未堆叠的数据重新排列成数据帧:
stacked.unstack()

这应该会将该系列恢复为原始数据帧。请注意,如果每个子组中不存在所有的值,拆堆将有可能产生丢失的数据。迷茫?好了,我们来看两个系列,series1series2,然后串联起来。到目前为止,一切都有道理。

  1. 现在,让我们解开连接的帧:
series1 = pd.Series([000, 111, 222, 333], index=['zeros','ones', 'twos', 'threes'])
series2 = pd.Series([444, 555, 666], index=['fours', 'fives', 'sixes'])

frame2 = pd.concat([series1, series2], keys=['Number1', 'Number2'])
frame2.unstack()

下面的屏幕截图显示了前面拆垛的输出:

由于在series1中没有foursfivessixes,它们的值在拆垛过程中存储为 NaN。同样的,series2中也没有onestwoszeros,所以对应的值都存储为 NaN。现在有道理了,对吧?很好。

*# 转化技术

合并数据库风格的数据帧部分,我们看到了如何合并不同类型的序列和数据帧。现在,让我们深入了解如何执行其他类型的数据转换,包括清理、过滤、重复数据消除等。

执行重复数据消除

您的数据框很可能包含重复的行。删除它们对于提高数据集的质量至关重要。这可以通过以下步骤完成:

  1. 让我们考虑一个简单的数据帧,如下所示:
frame3 = pd.DataFrame({'column 1': ['Looping'] * 3 + ['Functions'] * 4, 'column 2': [10, 10, 22, 23, 23, 24, 24]})
frame3

前面的代码创建了一个包含两列的简单数据框。您可以从下面的截图中清楚地看到,在这两列中,都有一些重复的条目:

  1. pandas数据框附带一个duplicated()方法,该方法返回一个布尔序列,说明哪些行是重复的:
frame3.duplicated()

前面代码的输出很容易解释:

表示True的行是包含重复数据的行。

  1. 现在,我们可以使用drop_duplicates()方法删除这些副本:
frame4 = frame3.drop_duplicates()
frame4

前面代码的输出如下:

请注意,第 1、4 和 6 行已被删除。基本上,duplicated()drop_duplicates()方法都会考虑所有的列进行比较。我们可以指定列的任何子集来检测重复的项目,而不是所有的列。

  1. 让我们添加一个新列,并尝试基于第二列查找重复的项目:
frame3['column 3'] = range(7)
frame5 = frame3.drop_duplicates(['column 2'])
frame5

前面片段的输出如下:

请注意,duplicateddrop_duplicates方法在复制删除过程中都保持第一个观察值。如果我们通过take_last=True参数,方法返回最后一个。

替换值

通常,在数据框中查找和替换一些值是很重要的。这可以通过以下步骤完成:

  1. 在这种情况下,我们可以使用replace方法:
import numpy as np
replaceFrame = pd.DataFrame({'column 1': [200., 3000., -786., 3000., 234., 444., -786., 332., 3332\. ], 'column 2': range(9)})
replaceFrame.replace(to_replace =-786, value= np.nan)

前面代码的输出如下:

请注意,我们刚刚用其他值替换了一个值。我们也可以一次替换多个值。

  1. 为此,我们使用列表显示它们:
replaceFrame = pd.DataFrame({'column 1': [200., 3000., -786., 3000., 234., 444., -786., 332., 3332\. ], 'column 2': range(9)})
replaceFrame.replace(to_replace =[-786, 0], value= [np.nan, 2])

在前面的代码中,有两个替换。所有-786值将被NaN代替,所有0值将被2代替。这很简单,对吧?

处理丢失的数据

每当有缺失值时,使用NaN值,这表示没有为该特定索引指定值。一个值成为NaN可能有几个原因:

  • 当从外部源检索数据并且数据集中有一些不完整的值时,就会发生这种情况。
  • 当我们连接两个不同的数据集并且某些值不匹配时,也会发生这种情况。
  • 由于数据收集错误,缺少值。
  • 当数据的形状改变时,会有新的额外的行或列未被确定。
  • 数据的重新索引可能会导致数据不完整。

让我们看看如何处理缺失的数据:

  1. 假设我们有一个数据帧,如下所示:
data = np.arange(15, 30).reshape(5, 3)
dfx = pd.DataFrame(data, index=['apple', 'banana', 'kiwi', 'grapes', 'mango'], columns=['store1', 'store2', 'store3'])
dfx

前面代码的输出如下:

假设我们在镇上有一个水果连锁店。目前,数据框显示了不同商店不同水果的销售情况。没有一家商店报告缺少值。

  1. 让我们在数据框中添加一些缺失的值:
dfx['store4'] = np.nan
dfx.loc['watermelon'] = np.arange(15, 19)
dfx.loc['oranges'] = np.nan
dfx['store5'] = np.nan
dfx['store4']['apple'] = 20.
dfx

现在输出将如下图所示:

注意,我们又增加了两个商店,store4store5,以及两种水果,watermelonoranges。假设我们知道从store4卖了多少公斤苹果和西瓜,但是我们没有从store5收集任何数据。此外,没有一家商店报告销售橙子。我们是一个巨大的水果经销商,不是吗?

请注意前面数据框中缺失值的以下特征:

  • 一整行可以包含NaN值。
  • 一整列可以包含NaN值。
  • 行和列中的一些(但不一定是全部)值可以是NaN

基于这些特征,让我们在下一节考察NaN值。

Pandas 对象中的 NaN 值

我们可以使用pandas库中的isnull()功能来识别NaN值:

  1. 检查以下示例:
dfx.isnull()

前面代码的输出如下:

请注意,真值表示NaN值。很明显,对吧?或者,我们也可以使用notnull()方法来做同样的事情。唯一的区别是函数将为非空值指示True

  1. 行动起来看看吧:
dfx.notnull()

其输出如下:

比较这两张表。这两个功能notnull()isnull()是相辅相成的。

  1. 我们可以使用sum()方法来统计每个商店中 NaN 值的数量。你问,这是怎么工作的?检查以下代码:
dfx.isnull().sum()

前面代码的输出如下:

store1 1
store2 1
store3 1
store4 5
store5 7
dtype: int64

10这一事实是求和的主要逻辑。前面的结果显示,store1store2store3没有报告一个值。store4未报 5 个值,store5未报 7 个值。

  1. 我们可以更深入地寻找缺失值的总数:
dfx.isnull().sum().sum()

前面代码的输出如下:

15

这表明15在我们的商店中缺少值。我们可以用另一种方法来找出实际报告了多少值。

  1. 因此,我们可以计算报告值的数量,而不是计算缺失值的数量:
dfx.count()

前面代码的输出如下:

store1 6
store2 6
store3 6
store4 2
store5 0
dtype: int64

很优雅,对吧?我们现在知道了两种不同的查找缺失值的方法,以及如何计算缺失值。

删除丢失的值

处理缺失值的方法之一是简单地将它们从我们的数据集中移除。我们已经看到,我们可以使用pandas库中的isnull()notnull()函数来确定空值:

dfx.store4[dfx.store4.notnull()]

前面代码的输出如下:

apple 20.0
watermelon 18.0
Name: store4, dtype: float64

输出显示store4只报了两项数据。现在,我们可以使用dropna()方法删除行:

dfx.store4.dropna()

前面代码的输出如下:

apple 20.0
watermelon 18.0
Name: store4, dtype: float64

请注意,dropna()方法只是通过删除带有 NaN 的行来返回数据帧的副本。原始数据帧不变。

如果dropna()应用于整个数据帧,那么它将删除数据帧中的所有行,因为在我们的数据帧中至少有一个 NaN 值:

dfx.dropna()

前面代码的输出是一个空的数据帧。

逐行删除

我们还可以删除具有 NaN 值的行。为此,我们可以使用how=all参数仅删除那些整数值完全为 NaN 的行:

dfx.dropna(how='all')

前面代码的输出如下:

请注意,只有橙色行被删除,因为这些整行都包含 NaN 值。

按列拖放

此外,我们还可以通过axis=1来指示按列检查 NaN。

检查以下示例:

dfx.dropna(how='all', axis=1)

前面代码的输出如下:

注意store5从数据框中删除。通过传入axis=1,我们指示 Pandas 如果列中的所有值都是NaN,则丢弃列。此外,我们还可以通过另一个参数thresh,来指定在删除列之前必须存在的最小数量的 nan:

dfx.dropna(thresh=5, axis=1)

前面代码的输出如下:

与前面的相比,请注意,现在连store4列都被删除了,因为它有五个以上的NaN值。

用 NaN 进行数学运算

对于数学运算,pandasnumpy库处理 NaN 值的方式不同。

考虑以下示例:

ar1 = np.array([100, 200, np.nan, 300])
ser1 = pd.Series(ar1)

ar1.mean(), ser1.mean()

前面代码的输出如下:

(nan, 200.0)

请注意以下事项:

  • 当 NumPy 函数遇到NaN值时,它返回NaN
  • 另一方面,Pandas 忽略NaN值,继续处理。执行求和操作时,NaN被视为 0。如果所有数值都是NaN,结果也是NaN

我们来计算一下store4卖出的水果总量:

ser2 = dfx.store4
ser2.sum()

前面代码的输出如下:

38.0

注意store4有五个 NaN 值。但是,在求和过程中,这些值被视为0,结果为38.0

同样,我们可以计算平均值,如下所示:

ser2.mean()

代码的输出如下:

19.0

请注意,NaNs 被视为 0。累积求和也是如此:

ser2.cumsum()

前面代码的输出如下:

apple 20.0
banana NaN
kiwi NaN
grapes NaN
mango NaN
watermelon 38.0
oranges NaN
Name: store4, dtype: float64

请注意,在计算累计总和时,只有实际值会受到影响。

填充缺失值

我们可以使用fillna()方法将 NaN 值替换为任何特定的值。

检查以下示例:

filledDf = dfx.fillna(0)
filledDf

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

注意,在前面的数据帧中,所有的NaN值都被0代替。将数值替换为0将影响几个统计数据,包括平均值、总和和中位数。

检查以下两个示例中的差异:

dfx.mean()

前面代码的输出如下:

store1 20.0
store2 21.0
store3 22.0
store4 19.0
store5 NaN
dtype: float64

现在,让我们使用以下命令根据填充的数据帧计算平均值:

filledDf.mean()

我们得到的输出如下:

store1 17.142857
store2 18.000000
store3 18.857143
store4 5.428571
store5 0.000000
dtype: float64

请注意,值略有不同。因此,用0填充可能不是最佳解决方案。

向后和向前填充

可以根据最后已知的值来填充 NaN 值。为了理解这一点,让我们考虑以我们的商店数据框架为例。

我们希望使用正向填充技术来填充store4:

dfx.store4.fillna(method='ffill')

前面代码的输出如下:

apple 20.0
banana 20.0
kiwi 20.0
grapes 20.0
mango 20.0
watermelon 18.0
oranges 18.0
Name: store4, dtype: float64

这里,从正向填充技术来看,最后一个已知的值是20,因此剩余的 NaN 值被它代替。

填充的方向可以通过改变method='bfill'来改变。检查以下示例:

dfx.store4.fillna(method='bfill')

前面代码的输出如下:

apple 20.0
banana 18.0
kiwi 18.0
grapes 18.0
mango 18.0
watermelon 18.0
oranges NaN
Name: store4, dtype: float64

注意这里的 NaN 值被18.0代替。

插值缺失值

Pandas 库为系列和数据帧提供interpolate()功能。默认情况下,它会对缺失值进行线性插值。检查以下示例:

ser3 = pd.Series([100, np.nan, np.nan, np.nan, 292])
ser3.interpolate()

前面代码的输出如下:

0 100.0
1 148.0
2 196.0
3 244.0
4 292.0
dtype: float64

你想知道这些值是如何计算的吗?这是通过在 NaN 值的任何序列之前和之后取第一个值来完成的。在前面的系列ser3中,第一个和最后一个值分别是100292。因此,它计算下一个值为 (292-100)/(5-1) = 48 。所以100之后的下一个值是 100 + 48 = 148

We can perform more complex interpolation techniques, especially with time series data. An example of this interpolation is shown in the notebook provided with this chapter.

接下来,我们将了解如何重命名轴索引。

重命名轴索引

考虑整形和旋转部分的例子。假设您想要将索引术语转换为大写字母:

dframe1.index = dframe1.index.map(str.upper)
dframe1

前面代码的输出如下:

请注意,索引已大写。如果我们想要创建数据帧的转换版本,那么我们可以使用rename()方法。当我们不想修改原始数据时,这种方法很方便。检查以下示例:

dframe1.rename(index=str.title, columns=str.upper)

代码的输出如下:

rename方法不复制数据帧。

离散化与宁滨

通常在处理连续数据集时,我们需要将它们转换成离散或区间形式。每一个区间都被称为一个仓位,因此名字宁滨开始发挥作用:

  1. 假设我们有一组学生的身高数据如下:
height = [120, 122, 125, 127, 121, 123, 137, 131, 161, 145, 141, 132]

我们希望将该数据集转换为 118 到 125、126 到 135、136 到 160,最后是 160 或更高的间隔。

  1. 要将前面的数据集转换成区间,我们可以使用pandas库提供的cut()方法:
bins = [118, 125, 135, 160, 200]
category = pd.cut(height, bins)
category

前面代码的输出如下:

[(118, 125], (118, 125], (118, 125], (125, 135], (118, 125], ..., (125, 135], (160, 200], (135, 160], (135, 160], (125, 135]] Length: 12 Categories (4, interval[int64]): [(118, 125] < (125, 135] < (135, 160] < (160, 200]]

如果你仔细观察输出,你会发现区间有数学符号。你还记得你小学数学课上的这些括号是什么意思吗?如果没有,这里简单回顾一下:

  • 括号表示侧面是敞开的。
  • 方括号表示它是封闭的或包含的。

从前面的代码块来看,(118, 125]表示左侧打开,右侧关闭。这在数学上表示如下:

因此,118不包括在内,但大于118的都包括在内,而125包括在区间内。

  1. 我们可以设置一个right=False参数来改变区间的形式:
category2 = pd.cut(height, [118, 126, 136, 161, 200], right=False)
category2

前面代码的输出如下:

[[118, 126), [118, 126), [118, 126), [126, 136), [118, 126), ..., [126, 136), [161, 200), [136, 161), [136, 161), [126, 136)] Length: 12 Categories (4, interval[int64]): [[118, 126) < [126, 136) < [136, 161) < [161, 200)]

注意,封闭性的输出形式已经改变。现在的结果是右闭,左开的形式。

  1. 我们可以使用pd.value_counts()方法检查每个箱中的数值数量:
pd.value_counts(category)

输出如下:

(118, 125] 5
(135, 160] 3
(125, 135] 3
(160, 200] 1
dtype: int64

输出显示区间[118-125)有五个值。

  1. 我们还可以通过传递标签列表来指示 bin 名称:
bin_names = ['Short Height', 'Average height', 'Good Height', 'Taller']
pd.cut(height, bins, labels=bin_names)

输出如下:

[Short Height, Short Height, Short Height, Average height, Short Height, ..., Average height, Taller, Good Height, Good Height, Average height]
Length: 12
Categories (4, object): [Short Height < Average height < Good Height < Taller]

请注意,我们至少传递了两个参数,即需要离散化的数据和所需的箱数。此外,我们使用了right=False参数来改变区间的形式。

  1. 现在,需要注意的是,如果我们只传递一个整数,它将根据数据中的最小值和最大值计算等长的容器。好吧,让我们验证一下我们在这里提到的:
import numpy as np
pd.cut(np.random.rand(40), 5, precision=2)

在前面的代码中,我们刚刚通过5作为所需的箱数,前面代码的输出如下:

[(0.81, 0.99], (0.094, 0.27], (0.81, 0.99], (0.45, 0.63], (0.63, 0.81], ..., (0.81, 0.99], (0.45, 0.63], (0.45, 0.63], (0.81, 0.99], (0.81, 0.99]] Length: 40
Categories (5, interval[float64]): [(0.094, 0.27] < (0.27, 0.45] < (0.45, 0.63] < (0.63, 0.81] < (0.81, 0.99]]

我们可以看到,根据垃圾箱的数量,它创建了五个类别。这里没有什么你不明白的,对吗?到目前为止干得不错。现在,让我们更进一步。数学中我们感兴趣的另一个术语是分位数。还记得这个概念吗?如果没有,不要担心,因为我们将在第 5 章 描述性统计中了解分位数和其他度量。现在,理解分位数将概率分布的范围划分为具有相似概率的连续区间就足够了。

Pandas 提供了一种qcut方法,该方法基于样本分位数形成箱。让我们用一个例子来验证这一点:

randomNumbers = np.random.rand(2000)
category3 = pd.qcut(randomNumbers, 4) # cut into quartiles
category3

前面代码的输出如下:

[(0.77, 0.999], (0.261, 0.52], (0.261, 0.52], (-0.000565, 0.261], (-0.000565, 0.261], ..., (0.77, 0.999], (0.77, 0.999], (0.261, 0.52], (-0.000565, 0.261], (0.261, 0.52]]
Length: 2000
Categories (4, interval[float64]): [(-0.000565, 0.261] < (0.261, 0.52] < (0.52, 0.77] < (0.77, 0.999]]

请注意,根据我们设置为4的箱数,它将我们的数据转换为四个不同的类别。如果我们计算每个类别中的值的数量,我们应该根据我们的定义得到相等大小的箱。让我们使用以下命令来验证这一点:

pd.value_counts(category3)

该命令的输出如下:

(0.77, 0.999] 500
(0.52, 0.77] 500
(0.261, 0.52] 500
(-0.000565, 0.261] 500
dtype: int64

我们的索赔因此得到核实。每个类别包含相同大小的500值。注意,与cut类似,我们也可以通过自己的垃圾箱:

pd.qcut(randomNumbers, [0, 0.3, 0.5, 0.7, 1.0])

前面代码的输出如下:

[(0.722, 0.999], (-0.000565, 0.309], (0.309, 0.52], (-0.000565, 0.309], (-0.000565, 0.309], ..., (0.722, 0.999], (0.722, 0.999], (0.309, 0.52], (-0.000565, 0.309], (0.309, 0.52]] Length: 2000
Categories (4, interval[float64]): [(-0.000565, 0.309] < (0.309, 0.52] < (0.52, 0.722] < (0.722, 0.999]]

请注意,它基于我们的代码创建了四个不同的类别。恭喜你!我们成功地学习了如何将连续数据集转换为离散数据集。

离群点检测和过滤

异常值是由于几个原因而与其他观测值有差异的数据点。在 EDA 阶段,我们的常见任务之一是检测和过滤这些异常值。这种异常值检测和过滤的主要原因是这种异常值的存在会在统计分析中引起严重的问题。在本节中,我们将执行简单的异常值检测和过滤。让我们开始吧:

  1. 按如下方式加载可从 GitHub 链接获得的数据集:
df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/hands-on-exploratory-data-analysis-with-python/master/Chapter%204/sales.csv')
df.head(10)

数据集是通过创建脚本手动合成的。如果您有兴趣了解我们是如何创建数据集的,可以在与本书共享的 GitHub 存储库中名为Chapter 4的文件夹中找到该脚本。

前面df.head(10)命令的输出如下截图所示:

  1. 现在,假设我们想根据销售数量和单价计算总价。我们可以简单地添加一个新列,如下所示:
df['TotalPrice'] = df['UnitPrice'] * df['Quantity']
df

这应该会添加一个名为TotalPrice的新列,如下图所示:

现在,让我们根据前面的表格回答一些问题。

让我们找到超过 3,000,000 的交易:

TotalTransaction = df["TotalPrice"]
TotalTransaction[np.abs(TotalTransaction) > 3000000]

前面代码的输出如下:

2 3711433
7 3965328
13 4758900
15 5189372
17 3989325
         ... 
9977 3475824
9984 5251134
9987 5670420
9991 5735513
9996 3018490
Name: TotalPrice, Length: 2094, dtype: int64

请注意,在前面的示例中,我们假设任何大于 3,000,000 的价格都是异常值。

如果TotalPrice大于6741112,显示上表中的所有列和行,如下所示:

df[np.abs(TotalTransaction) > 6741112]

前面代码的输出如下:

注意,在输出中,所有的TotalPrice值都大于6741112。我们可以使用任何类型的条件,行方式或列方式,来检测和过滤异常值。

排列和随机抽样

好了,现在我们有更多的数学术语需要学习:排列随机抽样。让我们看看如何使用pandas库执行排列和随机采样:

  1. 利用 NumPy 的numpy.random.permutation()功能,我们可以随机选择或置换数据帧中的一系列行。让我们用一个例子来理解这一点:
dat = np.arange(80).reshape(10,8)
df = pd.DataFrame(dat)
df

前面代码的输出如下:

  1. 接下来,我们称之为np.random.permutation()法。这个方法接受一个参数——我们需要被置换的轴的长度——并给出一个整数数组来指示新的顺序:
sampler = np.random.permutation(10)
sampler

前面代码的输出如下:

array([1, 5, 3, 6, 2, 4, 9, 0, 7, 8])
  1. 前面的输出数组用于pandas库中take()函数的基于 ix 的索引。查看以下示例以获得澄清:
df.take(sampler)

前面代码的输出如下:

理解输出是很重要的。请注意,我们的采样器数组包含array([1, 5, 3, 6, 2, 4, 9, 0, 7, 8])。这些数组项目中的每一个都代表原始数据帧的行。因此,从原始数据帧中,它提取第一行,然后是第五行,然后是第三行,依此类推。将此与原始数据帧输出进行比较,会更有意义。

无替换随机抽样

要在不替换的情况下计算随机抽样,请执行以下步骤:

  1. 为了在不替换的情况下执行随机采样,我们首先创建一个permutation数组。
  2. 接下来,我们切掉数组的第一个 n 元素,其中 n 是您想要采样的子集的期望大小。
  3. 然后我们使用df.take()方法获取实际样本:
df.take(np.random.permutation(len(df))[:3])

前面代码的输出如下:

请注意,在前面的代码中,我们只指定了大小为3的样本。因此,我们在随机样本中只得到三行。

替换随机抽样

要通过替换生成随机采样,请遵循给定的步骤:

  1. 我们可以使用numpy.random.randint()方法生成一个替换的随机样本,并绘制随机整数:
sack = np.array([4, 8, -2, 7, 5])
sampler = np.random.randint(0, len(sack), size = 10)
sampler

我们使用np.random.randint()方法创建了采样器。前面代码的输出如下:

array([3, 3, 0, 4, 0, 0, 1, 2, 1, 4])
  1. 现在,我们可以draw所需的样本:
draw = sack.take(sampler)
draw

前面代码的输出如下:

array([ 7,  7,  4,  5,  4,  4,  8, -2,  8,  5])

比较采样器的索引,然后将其与原始数据帧进行比较。在这种情况下,结果非常明显。

计算指标/虚拟变量

通常,我们需要将一个分类变量转换成一些虚拟矩阵。特别是对于统计建模或机器学习模型开发,创建虚拟变量至关重要。让我们开始吧:

  1. 假设我们有一个包含性别和投票数据的数据框架,如下所示:
df = pd.DataFrame({'gender': ['female', 'female', 'male', 'unknown', 'male', 'female'], 'votes': range(6, 12, 1)})
df

前面代码的输出如下:

到目前为止,没有什么太复杂的。然而,有时我们需要用10值以矩阵形式编码这些值。

  1. 我们可以使用pd.get_dummies()功能来实现:
pd.get_dummies(df['gender'])

前面代码的输出如下:

注意模式。原始数据框中有五个值,有三个唯一的值malefemaleunknown。每个唯一值被转换为一列,每个原始值被转换为一行。例如,在原始数据帧中,第一个值是female,因此它与female值中的1一起作为一行添加,其余的是0值,以此类推。

  1. 有时,我们想给列添加一个前缀。我们可以通过添加前缀参数来实现,如下所示:
dummies = pd.get_dummies(df['gender'], prefix='gender')
dummies

前面代码的输出如下:

注意添加到每个列名的gender前缀。没那么难,对吧?到目前为止做得很好。

让我们在下一节研究另一种类型的转换。

字处理

网上发现的很多数据都是以文本的形式出现的,操纵这些字符串是数据转换的一个基本部分。检查字符串操作的各个方面超出了本书的范围。但是,我们已经在附录中总结了主要的管柱操作。我们强烈建议查看附录,以便理解字符串函数。

数据转换的好处

到目前为止,我们已经看到了几个有用的数据转换用例。

让我们试着列出这些好处:

  • 数据转换促进了几个应用程序之间的互操作性。在数据集中创建类似格式和结构的主要原因是它与其他系统兼容。
  • 与更混乱的数据相比,当使用组织更好的数据时,人类和计算机的可理解性都得到提高。
  • 数据转换确保更高程度的数据质量,并保护应用程序免受一些计算挑战,如空值、意外重复、不正确的索引以及不兼容的结构或格式。
  • 数据转换确保了现代分析数据库和数据框架的更高性能和可扩展性。

在下一节中,我们将概述在数据转换工作中遇到的一些挑战。

挑战

讨论了数据转换的好处后,值得讨论一些值得注意的挑战。数据转换过程可能具有挑战性,原因有几个:

  • 它需要一个合格的专家团队和最先进的基础设施。获得这些专家和基础设施的成本会增加运营的成本。
  • 数据转换需要在数据转换和数据迁移之前进行数据清理。这个清洗过程可能是昂贵的耗时的
  • 通常,数据转换的活动涉及批处理。这意味着有时,我们可能需要等待一天,才能准备好下一批数据进行清理。这可能非常

摘要

在这一章中,我们讨论了几种数据争论技术,包括数据库风格的帧合并、沿着轴的连接、组合不同的帧、整形、去除重复、重命名轴索引、离散化和宁滨、检测和过滤异常值以及变换函数。我们使用了不同的数据集来理解不同的数据转换技术。

在下一章中,我们将详细讨论不同的描述性统计度量,包括中心趋势的度量和离差的度量。此外,我们将使用 Python 3 和不同的库,包括 SciPy、Pandas 和 NumPy,来理解这样的描述性度量。

进一步阅读

  • Pandas 食谱:使用 Python 进行科学计算、时间序列分析和数据可视化的食谱,第一版,作者: Theodore Petrou,Packt,2017
  • 掌握 Pandas,第二版,作者:阿希什·库马尔,帕克特,2019 年 10 月 25 日
  • 学习 Pandas,第二版,作者迈克尔·海德特,帕克特,2017 年 6 月 29 日
  • Petr Aubrecht,Zdenek 考巴 : 元数据驱动的数据转换;检索自http://labe.felk.cvut.cz/~aubrech/bin/Sumatra.pdf*

五、描述性统计学

在本章中,我们将探讨描述性统计及其各种技术。顾名思义,描述性统计通过提供与所提供的数据集相关的简短摘要来帮助描述和理解数据集。最常见的描述性统计类型包括中心倾向的度量、偏差的度量以及其他。在这一章中,我们将熟悉这些技术,并通过可视化来探索这些实际测量。我们将使用盒子图等工具从统计数据中获取知识。

在本章中,我们将涵盖以下主题:

  • 理解统计
  • 集中趋势测量
  • 分散的度量

技术要求

本章的代码可以在本书的 GitHub 资源库中找到,位于Chapter 5文件夹内:

理解统计

在数据科学中,定性和定量分析都是重要的方面。特别是,任何数据集的定量分析都需要理解统计概念。统计学是数学的一个分支,研究收集、组织和解释数据。因此,通过使用统计概念,我们可以了解数据的性质、数据集的摘要以及数据的分布类型。

分布函数

为了理解分布函数的概念,理解连续函数的概念是至关重要的。那么,当我们提到一个concontinuous函数时,我们指的是什么呢?基本上,连续函数是任何在值上没有任何意外变化的函数。这些突然或意外的变化被称为不连续。例如,考虑以下三次函数:

如果你画出这个函数的图,你会发现在这个系列的值中没有跳跃或空洞。因此,该功能是连续的:

了解了连续函数之后,现在让我们试着了解一下概率密度函数 ( PDF )是什么。PDF 可以用连续函数来定义,换句话说,对于任何连续函数,PDF 是变量具有 x 值的概率。

现在,如果你一直在关注,一个显而易见的问题应该会出现在你的脑海中。如果函数与离散的随机变量而不是连续的随机变量相关联呢?那么这个函数被称为概率质量函数(PMF)。关于更正式的定义,请参考中的参考文献【6】,进一步阅读部分。

离散随机变量的概率** 能力 分布概率 f 函数是一个概率列表,与每个可达到的值相关联。让我们假设一个随机变量,A,取实数区间上的所有值。然后,A 在结果列表 Z 中的概率,P(Z),是在 Z 之上和描述满足以下条件的函数 p(a)的曲线之下的区域:

  1. 曲线不能有负值(所有 a 的 p(a) > 0)。
  2. 曲线下的总面积始终等于 1。

这种曲线被称为密度曲线。连续概率分布包括正态分布、指数分布、均匀分布、伽玛分布、泊松分布和二项式分布。

均匀分布

任何连续均匀分布的均匀概率分布函数由下式给出:

让我们使用 Python 库、seaborn 和 matplotlib 绘制均匀分布的图表。首先,让我们导入生成图形所需的重要库:

import matplotlib.pyplot as plt
from IPython.display import Math, Latex
from IPython.core.display import Image
import seaborn as sns

sns.set(color_codes=True)
sns.set(rc={'figure.figsize':(10,6)})

现在,让我们生成一个均匀分布:

from scipy.stats import uniform
number = 10000
start = 20
width = 25

uniform_data = uniform.rvs(size=number, loc=start, scale=width)
axis = sns.distplot(uniform_data, bins=100, kde=True, color='skyblue', hist_kws={"linewidth": 15})
axis.set(xlabel='Uniform Distribution ', ylabel='Frequency')

代码很明显,对吧?我们只需从统计库中导入统一函数并生成数据。一旦生成数据集,我们就绘制图表。前面代码的输出图如下:

uniform函数用于在给定的开始位置(loc)和参数宽度(scale)之间生成一个统一的连续变量。size参数指定所考虑的随机变量的数量。该图说明了数据集是均匀分布的事实。

正态分布

Norm al 分布,或 Gaussia n 分布是一个函数,它将随机变量列表分布在一个形状像对称钟的图形中。我非常肯定,在你的数据科学生涯中,你会多次遇到这个术语。但是你了解它的概念吗?嗯,正态分布有一条关于其平均值对称的密度曲线,其分布通常由其标准差来定义。它有两个参数——均值和标准差。正态分布主要基于中心极限定理,这一事实使它具有相关性。如果一个总体中所有可能样本的大小为n,均值为*μ*,方差为*σ2*,则分布接近正态分布。数学上,给出如下:

现在,让我们看看如何使用 Python stats库绘制正态分布的插图:

from scipy.stats import norm

normal_data = norm.rvs(size=90000,loc=20,scale=30)
axis = sns.distplot(normal_data, bins=100, kde=True, color='skyblue', hist_kws={"linewidth": 15,'alpha':0.568})
axis.set(xlabel='Normal Distribution', ylabel='Frequency')

前面代码的输出如下:

我们可以通过norm.rvs()方法使用scipy.stats模块得到正态分布图。它允许loc参数设置分布的平均值,scale参数设置标准差,最后size参数指示随机变量的数量。

指数分布

一些事件以恒定的平均速率连续独立发生的过程被称为泊肃T2【n】T3T6】proT8 过程。指数分布描述了这种泊松点过程中事件之间的时间,指数分布的概率密度函数如下:

通过应用expon.rvs()函数,我们可以使用scipy.stats模块可视化指数分布的随机变量。检查以下代码:

# Exponential distribution
from scipy.stats import expon

expon_data = expon.rvs(scale=1,loc=0,size=1000)
axis = sns.distplot(expon_data, kde=True, bins=100, color='skyblue', hist_kws={"linewidth": 15})
axis.set(xlabel='Exponential Distribution', ylabel='Frequency')

前面代码的输出如下:

上图所示的图表说明了递减的指数函数。曲线在 x 轴上递减。

二项分布

顾名思义,二项式分布只有两种可能的结果,成功或失败。结果不需要同样可能,每个试验都是相互独立的。

让我们通过binom方法使用scipy.stats模块生成二项式分布图:

from scipy.stats import binom

binomial_data = binom.rvs(n=10, p=0.8,size=10000)

axis = sns.distplot(binomial_data, kde=False, color='red', hist_kws={"linewidth": 15})
axis.set(xlabel='Binomial Distribution', ylabel='Frequency')

下图给出了上述代码的输出:

来自scipy.stats模块的binom.rvs()方法将n作为试参数,p作为成功概率作为形状参数生成图形。

累积分布函数

现在,cumudistribution解决方案** 函数 ( CDF )是变量取小于等于x的值的概率。数学上,它写如下:**

**

当分布是标量连续时,它提供 PDF 下的面积,范围从负无穷大到 CDF 指定多元随机变量的分布。

描述性统计学

描述性统计处理简单的数据摘要的表述,以便清楚地理解它们。数据摘要可以是数字表示,也可以是简单图形的可视化,以便进一步理解。通常,这样的总结有助于统计分析的初始阶段。描述性统计有两种类型:

  1. 集中趋势测量
  2. 可变性的度量(扩散)

中心趋势的度量包括meanmedianmode,而可变性的度量包括standard deviation(或方差)、变量的最小值和最大值、kurtosisskewness。我们将在下一节讨论这两个类别。

集中趋势测量

中心趋势的度量倾向于描述数据集的平均值或均值,该平均值或均值被认为提供了整个度量集的最佳汇总。该值在某种程度上是集合的核心。分析数据分布频率最常用的方法是平均值、中位数和众数。

平均值

平均值是一个数字,观察到的连续变量分布在这个数字周围。这个数字估计整个数据集的值。从数学上来说,它是数据集中数字的总和除以整数的结果。

设 x 是一组整数:

x = (12,2,3,5,8,9,6,4,2)

因此, x 的平均值可以计算如下:

接下来,我们来看看中位数。

中位数

给定按升序或降序排序的数据集,中位数将数据分为两部分。计算中位数的一般公式如下:

这里,n是数据中的项数。计算中位数的步骤如下:

  1. 以升序或降序对数字进行排序。
  2. 如果n是奇数,求(n+1)/2<sup>th</sup>项。该术语对应的值是中位数。
  3. 如果n为偶数,求(n+1)/2<sup>th</sup>项。中间值是中间位置两边数字的平均值。

对于x这样的一组整数,我们必须按照升序排列,然后选择中间的整数。

x按升序= (2,2,3,4,5,6,8,9,12)。

这里,中位数是 5。

方式

模式是在数据集中出现次数最多的整数。它恰好是数据集中出现频率最高的值。在中值示例的x数据集中,模式为 2,因为它在集合中出现两次。

Python 提供了不同的库来操作数据集中的描述性统计。常用的库有pandasnumpyscipy。这些中心趋势的度量可以简单地通过numpypandas功能来计算。

为了实践描述性统计,我们需要一个包含多个数字记录的数据集。这是一个汽车数据集,收集了汽车的不同特征和属性,如象征、标准化损失、渴望和许多其他特征,对这些特征和属性的分析将提供一些与该数据集中的汽车相关的有价值的见解和发现。

让我们从导入所需的数据集和 Python 库开始:

import pandas as pd
import numpy as np

现在,让我们加载汽车数据库:

df = pd.read_csv("data.csv")
df.head()

在前面的代码中,我们假设数据库存储在当前驱动器中。或者,您可以将路径更改到正确的位置。到目前为止,您应该已经熟悉了数据加载技术。代码的输出如下所示:

Data cleaning:In the previous chapter, we discussed several ways in which we can clean our dataset. We need to clean numeric columns. Since we have already discussed several ways in which we can clean the dataset, I have skipped the codes for doing so. However, you can find a section entitled Data cleaning in the Python notebook attached to this chapter in the GitHub repository.

现在,让我们从计算中心倾向的度量开始。在为所有行建立这些之前,让我们看看如何获得单个列的中心趋势。例如,我们希望获得代表高度的列的平均值、中间值和模式。在 Pandas 中,我们可以通过将列名指定为dataframe["column_name"]来轻松获得单个列。在我们的例子中,我们的数据帧存储在df变量中。因此,我们可以得到所有的身高数据项目作为df["height"]。现在,Pandas 提供了简单的内置功能来测量中枢倾向。让我们这样计算:

height =df["height"]
mean = height.mean()
median =height.median()
mode = height.mode()
print(mean , median, mode)

前面代码的输出如下:

53.766666666666715 54.1 0 50.8
dtype: float64

现在,重要的是解释结果。光是这些简单的统计就可以明白,汽车的平均高度在53.766左右,模式值为50.8的汽车很多。同样,我们可以获得数据类型为数字的任何列的中心趋势的度量。下面的屏幕截图显示了类似的有用功能列表:

除了查找单个列的统计信息之外,还可以一次查找整个数据集的描述性统计信息。Pandas 为此提供了一个非常有用的功能df.describe:

df.describe()

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

如果你以前用过 Pandas,我很肯定你听说过或者可能用过几次这个功能。但是你真的了解你获得的输出吗?在上表中,您可以看到我们有所有列的统计数据,不包括 NaN 值。该函数在计算过程中同时考虑了数值序列和对象序列。在这些行中,我们得到该列的计数、平均值、标准偏差、最小值、百分位数和最大值。我们可以更好地理解我们的数据集。事实上,如果您查看前面的表格,您可以回答以下问题:

  • 我们数据集中的总行数是多少?
  • 汽车的平均长度、宽度、高度、价格和压缩比是多少?
  • 汽车的最小高度是多少?汽车的最大高度是多少?
  • 汽车整备质量的最大标准偏差是多少?。

事实上,我们现在可以回答很多问题,仅仅基于一张表。挺好的,对吧?现在,每当您开始任何数据科学工作时,执行一些健全性检查总是被认为是好的做法。我所说的健全性检查,是指在实际拟合机器学习模型之前了解你的数据。获取数据集的描述就是这样一种健全性检查。

在分类变量具有离散值的情况下,我们可以使用value_counts()函数来总结分类数据。嗯,身教胜于言教。在我们的数据集中,我们有一个分类数据列make。我们先按照这样的类别统计条目总数,然后取前 30 个最大值,画一个条形图:

df.make.value_counts().nlargest(30).plot(kind='bar', figsize=(14,8))
plt.title("Number of cars by make")
plt.ylabel('Number of cars')
plt.xlabel('Make of the cars')

到目前为止,前面的代码应该很熟悉了。我们正在使用 Pandas 图书馆的value_counts()功能。一旦我们有了列表,我们就可以使用nlargest()函数获得前 30 个最大值。最后,我们利用 Pandas 图书馆提供的绘图功能。这里显示了前面代码片段的输出:

如图所示,表格很有帮助。为了增加理解的程度,我们可以使用可视化技术,如上图所示。从图表中可以很清楚地看出,丰田品牌是最受欢迎的品牌。同样,我们可以很容易地在列表中可视化连续的品牌。

分散的度量

第二类描述性统计是离差的度量,也称为变异性的度量。它用于描述数据集的可变性,数据集可以是样本或总体。它通常与中心趋势的度量结合使用,以提供一组数据的总体描述。对分散性/可变性/分散性的衡量让我们了解了中心趋势在多大程度上代表了数据。如果我们仔细分析数据集,有时平均值可能不是数据的最佳表示,因为当数据之间存在较大差异时,平均值会有所不同。在这种情况下,离差的度量将更准确地表示数据集中的可变性。

多种技术在我们的数据集中提供了分散的度量。一些常用的方法是标准差(或方差)、变量的最小值和最大值、范围、峰度和偏斜度。

标准偏差

用简单的语言来说,标准差是数据集中每个值与其平均值/均值之间的差值的平均值/均值;也就是说,数据是如何从平均值展开的。如果数据集的标准偏差较低,则数据点倾向于接近数据集的平均值,否则,数据点分布在更大的值范围内。

不同的 Python 库都有获取数据集标准差的函数。NumPy 库具有numpy.std(dataset)功能。统计库有statistics.stdev(dataset)。功能。使用 pandas 库,我们使用df.std()函数计算df数据框中的标准偏差:

#standard variance of dataset using std() function
std_dev =df.std()
print(std_dev)
# standard variance of the specific column
sv_height=df.loc[:,"height"].std()
print(sv_height)

前面代码的输出如下:

接下来,让我们看看方差。

差异

方差是数据集中每个值与其平均值/均值之差的平均值/均值的平方;也就是说,它是标准差的平方。

不同的 Python 库都有获取数据集方差的函数。NumPy 库具有numpy.var(dataset)功能。统计库具有statistics.variance(dataset)功能。使用 Pandas 库,我们使用df.var()函数计算df数据框中的方差:

# variance of dataset using var() function
variance=df.var()
print(variance)

# variance of the specific column
var_height=df.loc[:,"height"].var()
print(var_height)

前面代码的输出如下:

有必要从这里提供的代码片段中注意到以下观察结果:

  • 需要注意的是df.var()默认情况下会计算给定数据帧中跨列的方差。另外,我们可以指定axis=0表示需要按列或按行计算方差。
  • 指定df.var(axis=1)将计算给定数据帧中的行方向方差。
  • 最后,还可以通过指定位置来计算任何特定列中的方差。例如,df.loc[:,"height"].var()计算前面数据集中列高度的方差。

歪斜

在概率论和统计学中,偏斜度是数据集中变量关于其平均值的不对称性的度量。偏斜度值可以是正的或负的,或者是未定义的。偏斜度值告诉我们数据是偏斜的还是对称的。下面是一个正倾斜数据集、对称数据和一些负倾斜数据的示例:

请注意上图中的以下观察结果:

  • 右边的图的尾巴比右边的长。这表明数据的分布向左倾斜。如果选择左侧长尾巴中的任何一点,平均值小于模式。这种情况被称为 偏度
  • 左侧的图表在右侧有一条较长的尾巴。如果选择右侧尾部的任何点,平均值大于模式。这种情况被称为 偏度
  • 中间的图有一个右手边的尾巴,和左手边的尾巴一样。这种状态被称为对称状态

不同的 Python 库都有获取数据集偏斜度的函数。SciPy 库有一个scipy.stats.skew(dataset)功能。使用 Pandas 库,我们可以使用df.skew()函数计算df数据帧中的偏斜度。

这里,在我们的汽车数据框架中,让我们使用df.skew()函数来获得偏斜度:

df.skew()

前面代码的输出如下:

此外,我们还可以在列级别计算偏斜。例如,柱高的偏斜可以使用df.loc[:,"height"].skew()来计算。功能。

峭度

我们已经讨论过正态分布。你还记得钟形图吗?如果没有,就再看一遍本章的第一节。你很可能会问自己,你为什么要记得这些?为了理解峰度的概念,这是必要的。基本上,峰度是一个统计量,说明分布的尾部与正态分布的尾部有多大的不同。这种技术可以识别给定的分布是否包含极值。

But hold on, isn't that similar to what we do with skewness? Not really. Skewness typically measures the symmetry of the given distribution. On the other hand, kurtosis measures the heaviness of the distribution tails.

峰度,不像偏斜度,不是关于峰值或平坦度。它是给定分布中异常值存在的度量。峰度的高低都是数据需要进一步调查的指标。峰度越高,异常值越高。

峰度类型

峰度有三种类型——中峰度、细峰度和扁峰度。让我们一个一个来看:

  • 中库分布:如果任何数据集遵循正态分布,则它遵循中库分布。峰度在 0 左右。
  • 细峰度:这种情况下,分布的峰度大于 3,肥尾表示分布产生更多的异常值。
  • 板状峰度:在这种情况下,分布具有负峰度,与正态分布相比,尾部非常细。

下图显示了所有三种峰度:

不同的 Python 库都有获取数据集峰度的函数。SciPy 库具有scipy.stats.kurtosis(dataset)功能。使用 Pandas 库,我们使用df.kurt()函数计算df数据帧的峰度:

# Kurtosis of data in data using skew() function
kurtosis =df.kurt()
print(kurtosis)

# Kurtosis of the specific column
sk_height=df.loc[:,"height"].kurt()
print(sk_height)

这里给出了前面代码的输出:

同样,我们可以计算任何特定数据列的峰度。例如,我们可以将柱高的峰度计算为df.loc[:,"height"].kurt()

计算百分比

百分位数衡量任何数据集中低于特定值的值的百分比。为了计算百分位数,我们需要确保我们的列表是有序的。举个例子,如果你说第 80 个百分位数的数据是 130:那这意味着什么?嗯,这只是意味着 80%的数值低于 130。很简单,对吧?我们将对此使用以下公式:

假设我们有给定的数据:1,2,2,3,4,5,6,7,7,8,9,10。那么百分位值 4 = (4/12) * 100 = 33.33%。

这仅仅意味着 33.33%的数据小于 4。

现在,让我们从到目前为止一直使用的同一数据帧中计算height列的百分比:

height = df["height"]
percentile = np.percentile(height, 50,)
print(percentile)

前面代码的输出如下:

54.1

The preceding formula is very simple. But do you see any pattern with the measures of central tendencies? What would be the 50th percentile? This corresponds to the median. Were you able to deduce that?

四重奏乐团

给定按升序排序的数据集,四分位数是将给定数据集拆分为四分位数的值。四分位数指的是将给定数据集分成四个相等部分的三个数据点,这样每个分割就占数据集的 25%。就百分位数而言,第 25 个百分位数被称为第一个四分位数(Q1),第 50 个百分位数被称为第二个四分位数(Q2),第 75 个百分位数被称为第三个四分位数(Q3)。

基于四分位数,还有另一个度量称为四分位数间范围,它也度量数据集的可变性。其定义如下:

IQR 不受异常值的影响。让我们从到目前为止一直使用的相同数据帧中获取price列的 IQR:

price = df.price.sort_values()
Q1 = np.percentile(price, 25)
Q2 = np.percentile(price, 50)
Q3 = np.percentile(price, 75)

IQR = Q3 - Q1
IQR

前面片段的输出如下:

8718.5

接下来,让我们使用方框图来可视化四分位数。

可视化四分位数

首先,让我们生成一些数据。让我们假设以下是学生在三个不同科目中获得的分数:

scorePhysics = [34,35,35,35,35,35,36,36,37,37,37,37,37,38,38,38,39,39,40,40,40,40,40,41,42,42,42,42,42,42,42,42,43,43,43,43,44,44,44,44,44,44,45,45,45,45,45,46,46,46,46,46,46,47,47,47,47,47,47,48,48,48,48,48,49,49,49,49,49,49,49,49,52,52,52,53,53,53,53,53,53,53,53,54,54,54,54,54,54,54,55,55,55,55,55,56,56,56,56,56,56,57,57,57,58,58,59,59,59,59,59,59,59,60,60,60,60,60,60,60,61,61,61,61,61,62,62,63,63,63,63,63,64,64,64,64,64,64,64,65,65,65,66,66,67,67,68,68,68,68,68,68,68,69,70,71,71,71,72,72,72,72,73,73,74,75,76,76,76,76,77,77,78,79,79,80,80,81,84,84,85,85,87,87,88]

scoreLiterature = [49,49,50,51,51,52,52,52,52,53,54,54,55,55,55,55,56,56,56,56,56,57,57,57,58,58,58,59,59,59,60,60,60,60,60,60,60,61,61,61,62,62,62,62,63,63,67,67,68,68,68,68,68,68,69,69,69,69,69,69,70,71,71,71,71,72,72,72,72,73,73,73,73,74,74,74,74,74,75,75,75,76,76,76,77,77,78,78,78,79,79,79,80,80,82,83,85,88]

scoreComputer = [56,57,58,58,58,60,60,61,61,61,61,61,61,62,62,62,62,63,63,63,63,63,64,64,64,64,65,65,66,66,67,67,67,67,67,67,67,68,68,68,69,69,70,70,70,71,71,71,73,73,74,75,75,76,76,77,77,77,78,78,81,82,84,89,90]

现在,如果我们想要绘制单个对象的方框图,我们可以使用plt.boxplot()功能来完成:

plt.boxplot(scoreComputer, showmeans=True, whis = 99)

让我们打印计算机学科分数的方框图:

上图说明了这样一个事实,即盒子从上四分位数到下四分位数(大约 62 和 73),而胡须(从盒子中伸出的条)最小为 56,最大为 90。红线是中间值(大约 67),而小三角形(绿色)是平均值。

现在,让我们也给其他主题添加方框图。我们可以通过将所有分数组合成一个变量来轻松做到这一点:

scores=[scorePhysics, scoreLiterature, scoreComputer]

接下来,我们绘制方框图:

box = plt.boxplot(scores, showmeans=True, whis=99)

plt.setp(box['boxes'][0], color='blue')
plt.setp(box['caps'][0], color='blue')
plt.setp(box['caps'][1], color='blue')
plt.setp(box['whiskers'][0], color='blue')
plt.setp(box['whiskers'][1], color='blue')

plt.setp(box['boxes'][1], color='red')
plt.setp(box['caps'][2], color='red')
plt.setp(box['caps'][3], color='red')
plt.setp(box['whiskers'][2], color='red')
plt.setp(box['whiskers'][3], color='red')

plt.ylim([20, 95]) 
plt.grid(True, axis='y') 
plt.title('Distribution of the scores in three subjects', fontsize=18) 
plt.ylabel('Total score in that subject') 
plt.xticks([1,2,3], ['Physics','Literature','Computer'])

plt.show()

这里给出了前面代码的输出:

从图中可以明显看出,学生获得的最低分在 32 分左右,而获得的最高分是 90 分,属于计算机科学科目。

摘要

在本章中,我们讨论了描述性统计的几个方面。描述性统计通常指定量描述给定数据集的汇总统计。我们讨论了该领域中最常用的汇总度量,包括中心趋势度量(均值、中值和模式)和可变性度量(标准差、最小值、最大值、峰度和偏斜度)。

在下一章中,我们将继续使用分组技术进行更高级的描述性统计。这些分组技术由 Pandas 图书馆提供。

进一步阅读

  • 偏斜度和峰度的测量 :
  • Pandas 食谱西奥多·彼得罗帕克特出版
  • 学习 Pandas——第二版迈克尔·海特帕克特出版
  • 掌握 Pandas费米·安东尼帕克特出版
  • Pandas 动手数据分析斯蒂芬妮莫林帕克特出版****

六、分组数据集

在数据分析过程中,根据某些标准将数据聚集或分组通常是很重要的。例如,电子商务商店可能希望对圣诞节期间完成的所有销售或黑色星期五收到的订单进行分组。这些分组概念出现在数据分析的几个部分。在本章中,我们将介绍分组技术的基础,以及这样做如何改进数据分析。我们将讨论不同的groupby()机制,这些机制将我们的数据集累积到我们可以对其执行聚合的各种类中。我们还将弄清楚如何利用数据透视表和交叉列表来可视化地剖析这些分类数据。

在本章中,我们将涵盖以下主题:

  • 了解 groupby()
  • Groupby 力学
  • 数据聚合
  • 数据透视表和交叉列表

技术要求

本章的代码可以在本书的 GitHub 资源库中的Chapter 6文件夹中找到。

我们将在本章中使用的数据集可以通过 Kaggle 在开放访问下获得。可以从https://www.kaggle.com/toramky/automobile-dataset下载。

在本章中,我们将使用pandas库,所以请确保您已经安装了它。

了解 groupby()

在数据分析阶段,将数据集分为多个类别或组通常是至关重要的。我们可以使用pandas库进行这样的分类。Pandasgroupby功能是这样做最有效、最节省时间的功能之一。Groupby提供的功能允许我们在整个数据框中拆分-应用-合并;也就是说,该函数可用于拆分、应用和组合数据帧。

类似于结构化查询语言 ( SQL ),我们可以使用 Pandas 和 Python 通过使用任何接受pandas对象或numpy数组的内置函数来执行更复杂的组操作。

在下一节中,我们将使用pandas库来研究机械组。

Groupby 力学

在处理pandas数据帧时,我们的分析可能要求我们按照一定的标准分割数据。Groupby mechanics 将我们的数据集聚集到不同的类中,在这些类中我们可以执行练习和进行更改,例如:

  • 按特征分组,分级
  • 按组聚合数据集
  • 将自定义聚合函数应用于组
  • 分组转换数据集

Pandasgroupby方法执行两个基本功能:

  • 它根据一些标准将数据分成组。
  • 它对每个组独立应用一个函数。

为了使用groupby功能,我们需要一个数据集,其中有多个数字和分类记录,这样我们就可以根据不同的类别和范围进行分组。

让我们来看一个汽车数据集,它收集了汽车的不同特征和属性,如symbollingnormalized-lossesmakeaspirationbody-styledrive-wheelsengine-location等。让我们开始吧:

  1. 让我们从导入所需的 Python 库和数据集开始:
import pandas as pd
df = pd.read_csv("/content/automobileEDA.csv")
df.head()

这里,我们假设数据库存储在当前驱动器中。如果没有,您可以将路径更改到正确的位置。到目前为止,您应该已经熟悉了实现这一点的适当的数据加载技术,所以我们在这里不再赘述。

前面代码的输出如下:

如您所见,有多列带有分类变量。

  1. 使用groupby()函数,我们可以根据body-style列对该数据集进行分组:
df.groupby('body-style').groups.keys()

前面代码的输出如下:

dict_keys(['convertible', 'hardtop', 'hatchback', 'sedan', 'wagon'])

从前面的输出中,我们知道body-style列有五个唯一的值,包括convertiblehardtophatchbacksedanwagon

  1. 现在,我们可以根据body-style列对数据进行分组。接下来,让我们打印包含在具有convertiblebody-style值的组中的值。这可以使用以下代码来完成:
# Group the dataset by the column body-style
style = df.groupby('body-style')

# Get values items from group with value convertible 
style.get_group("convertible")

前面代码的输出如下:

在前面的例子中,我们使用单个body-style列进行分组。我们还可以选择列的子集。我们将在下一节学习如何做到这一点。

选择列的子集

为了基于多个类别形成组,我们可以简单地在groupby()函数中指定列名。分组将与第一个类别、第二个类别同时进行,依此类推。

让我们使用两个类别body-styledrive wheels,如下所示:

double_grouping = df.groupby(["body-style","drive-wheels"])
double_grouping.first()

前面代码的输出如下:

我们不仅可以用特定的标准对数据集进行分组,还可以同时对整个组直接执行算术运算,并将输出打印为系列或数据帧。有max()min()mean()first()last()等功能可以直接应用于GroupBy对象,以获取各组的汇总统计数据。

在下一节中,我们将逐一讨论这些函数。

最大和最小

让我们计算每个组的最大和最小条目。在这里,我们将找到normalized-losses列的最大值和最小值:

# max() will print the maximum entry of each group 
style['normalized-losses'].max()

# min() will print the minimum entry of each group 
style['normalized-losses'].min()

前面代码的输出如下:

body-style
convertible 122
hardtop 93
hatchback 65
sedan 65
wagon 74
Name: normalized-losses, dtype: int64

如前面的输出所示,给出了每个类别的最小值。

均值

我们可以找到每组中数值列的平均值。这可以使用df.mean()方法来完成。

求平均值的代码如下:

# mean() will print mean of numerical column in each group
style.mean()

前面代码的输出如下:

请注意,我们可以通过指定一个列来获得每个列的平均值,如下所示:

# get mean of each column of specific group
style.get_group("convertible").mean()

前面代码的输出如下:

接下来,我们还可以统计每组symboling/records的个数。为此,请使用以下代码:

# get the number of symboling/records in each group
style['symboling'].count()

前面代码的输出如下:

body-style
convertible 6
hardtop 8
hatchback 68
sedan 94
wagon 25
Name: symboling, dtype: int64

了解了计数部分后,在下一节中,我们将讨论不同类型的数据聚合技术。

数据聚合

聚合是对数据集或其子集实施任何数学运算的过程。聚合是 Pandas 中用于操纵数据框中的数据进行数据分析的众多技术之一。

Dataframe.aggregate()函数用于在一列或多列之间应用聚合。一些最常用的聚合如下:

  • sum:返回请求轴的值的总和
  • min:返回请求轴的最小值
  • max:返回请求轴的最大值

我们可以在DataFramedf中应用聚合,如df.aggregate()df.agg()

由于聚合仅适用于数值类型的列,因此让我们从数据集中提取一些数值列,并对它们应用一些聚合函数:

# new dataframe that consist length,width,height,curb-weight and price
new_dataset = df.filter(["length","width","height","curb-weight","price"],axis=1)
new_dataset

前面代码片段的输出如下:

接下来,让我们应用单个聚合来获取列的平均值。为此,我们可以使用agg()方法,如下代码所示:

# applying single aggregation for mean over the columns
new_dataset.agg("mean", axis="rows")

前面代码的输出如下:

length 0.837102
width 0.915126
height 53.766667
curb-weight 2555.666667
price 13207.129353
dtype: float64

我们可以将多个函数聚合在一起。例如,我们可以通过使用以下代码一次找到所有列的总和和最小值:

# applying aggregation sum and minimum across all the columns 
new_dataset.agg(['sum', 'min']) 

前面代码的输出如下:

输出是一个数据帧,其中的行包含应用于列的各个聚合的结果。要跨不同的列应用聚合函数,可以传递一个字典,该字典包含列名和值,这些列名和值包含任何特定列的聚合函数列表:

# find aggregation for these columns 
new_dataset.aggregate({"length":['sum', 'min'], 
              "width":['max', 'min'], 
              "height":['min', 'sum'], 
              "curb-weight":['sum']}) 
# if any specific aggregation is not applied on a column
# then it has NaN value corresponding to it

前面代码的输出如下:

检查前面的输出。行的最大值、最小值和总和代表每一列的值。请注意,有些值是基于其列值的NaN

分组操作

最重要的操作groupBy工具是聚合、过滤、转换和应用。在数据集中实现聚合函数的一种有效方法是在对所需的列进行分组后进行。聚合函数将为每个组返回一个聚合值。一旦创建了这些组,我们就可以对分组的数据应用一些聚合操作。

让我们通过传递一个聚合函数字典将DataFramedfbody-styledrive-wheels以及extract stats from each group分组:

# Group the data frame df by body-style and drive-wheels and extract stats from each group
df.groupby(
   ["body-style","drive-wheels"]
).agg(
    {
         'height':min, # minimum height of car in each group
         'length': max, # maximum length of car in each group
         'price': 'mean', # average price of car in each group

    }
)

前面代码的输出如下:

前面的代码根据body-styledriver-wheels对数据帧进行分组。然后,聚合函数应用于heightlengthprice列,这些列返回各自组中的最小高度、最大长度和平均价格。

我们可以为要分组执行的功能创建一个聚合字典,然后在以后使用它:

# create dictionary of aggregations
aggregations=(
    {
         'height':min, # minimum height of car in each group
         'length': max, # maximum length of car in each group
         'price': 'mean', # average price of car in each group

    }
)
# implementing aggregations in groups
df.groupby(
   ["body-style","drive-wheels"]
).agg(aggregations) 

前面代码的输出如下:

我们也可以在聚合中使用numpy函数:

# import the numpy library as np
import numpy as np
# using numpy libraries for operations
df.groupby(
   ["body-style","drive-wheels"])["price"].agg([np.sum, np.mean, np.std])

前面代码的输出如下:

如前面截图所示,我们选择了两个类别,body-styledrive-wheels。这里可以看到每行的总和、平均值和标准差。很简单,对吧?现在,让我们学习如何重命名分组聚合列。

重命名分组聚合列

如果我们可以用在该列或组中执行的操作来重命名列名,您不认为输出数据帧会更有信息吗?

我们可以在每个组中执行聚合,并根据执行的操作重命名列。这对于理解输出数据集非常有用:

df.groupby(
   ["body-style","drive-wheels"]).agg(
    # Get max of the price column for each group
    max_price=('price', max),
    # Get min of the price column for each group
    min_price=('price', min),
    # Get sum of the price column for each group
    total_price=('price', 'mean') 
)

前面代码的输出如下:

如上图截图所示,我们只选择了两类:body-styledrive-wheels。对于这些类别中的每一行,最高价、最低价和总价都是在连续的列中计算的。

分组转换

使用groupby()和聚合,你一定想过,为什么我们不能将数据分组,应用聚合,并将结果直接追加到数据框中? 这一切有可能一步到位吗?是的,它是。

对组或列执行转换会返回一个对象,该对象按与其自身相同的轴长度进行索引。这是一个结合groupby()使用的操作。聚合操作必须返回数据的简化版本,而转换操作可以返回完整数据的转换版本。让我们来看看:

  1. 让我们首先使用一个简单的转换函数,使用lambda函数将每辆车的价格提高 10%:
df["price"]=df["price"].transform(lambda x:x + x/10)
df.loc[:,'price']

前面代码的输出如下:

0 14844.5
1 18150.0
2 18150.0
3 15345.0
4 19195.0
        ... 
196 18529.5
197 20949.5
198 23633.5
199 24717.0
200 24887.5
Name: price, Length: 201, dtype: float64
  1. 让我们通过body-styledrive-wheels来观察每个分组的汽车平均价格:
df.groupby(["body-style","drive-wheels"])["price"].transform('mean')

前面代码的输出如下:

0 26344.560000
1 26344.560000
2 15771.555556
3 10792.980000
4 13912.066667
           ... 
196 23883.016667
197 23883.016667
198 23883.016667
199 23883.016667
200 23883.016667
Name: price, Length: 201, dtype: float64

如果您查看前面的输出,您会注意到这是如何从我们正常的groupby()函数返回不同大小的数据集的。

  1. 现在,为原始数据框中的平均价格创建一个新列:
df["average-price"]=df.groupby(["body-style","drive-wheels"])["price"].transform('mean')

# selecting columns body-style,drive-wheels,price and average-price
df.loc[:,["body-style","drive-wheels","price","average-price"]]

前面代码的输出如下:

前面截图中显示的输出非常明显。我们计算了两个类别的价格和平均价格:body-styledrive-wheels。接下来,我们将讨论如何使用透视表和交叉制表技术。

数据透视表和交叉列表

Pandas 为分组和汇总数据提供了几种选择。我们已经讨论了groupby、聚合和转换,但是还有其他选项可用,例如pivot_tablecrosstab。首先,让我们了解透视表。

数据透视表

pandas.pivot_table()函数创建一个电子表格样式的数据透视表作为数据框。数据透视表中的级别将存储在结果数据框的索引和列上的多索引对象(分层索引)中。

最简单的数据透视表必须有一个数据框和一个索引/索引列表。让我们看看如何做到这一点:

  1. 让我们制作一个由body-styledrive-wheelslengthwidthheightcurb-weightprice列组成的新数据框的透视表:
new_dataset1 = df.filter(["body-style","drive-wheels",
                          "length","width","height","curb-weight","price"],axis=1)
#simplest pivot table with dataframe df and index body-style
table = pd.pivot_table(new_dataset1, index =["body-style"]) 
table

前面代码的输出如下:

输出表类似于我们如何根据body-style对数据帧进行分组。上表中的值是相应类别中值的平均值。让我们制作一个更精确的透视表。

  1. 现在,用new_dataset1数据框设计一个透视表,以body-styledrive-wheels为索引。请注意,提供多个索引将首先对数据帧进行分组,然后汇总数据:
#pivot table with dataframe df and index body-style and drive-wheels
table = pd.pivot_table(new_dataset1, index =["body-style","drive-wheels"]) 
table

前面代码的输出如下:

输出是按body-styledrive-wheels分组的透视表。它包含相应列数值的平均值。

数据透视表的语法包含一些参数,如 c、值、索引、列和聚合函数。我们可以同时将聚合函数应用于透视表。我们可以传递聚合函数、值和要应用聚合的列,以便创建数据框的汇总子集的数据透视表:

# import numpy for aggregation function
import numpy as np

# new data set with few columns
new_dataset3 = df.filter(["body-style","drive-wheels","price"],axis=1)

table = pd.pivot_table(new_dataset3, values='price', index=["body-style"],
                       columns=["drive-wheels"],aggfunc=np.mean,fill_value=0)
table

就语法而言,前面的代码表示以下内容:

  • 数据集名为new_dataset3的透视表。
  • 这些值是要应用聚合函数的列。
  • 索引是用于对数据进行分组的列。
  • 用于指定数据类别的列。
  • aggfunc是要应用的聚合函数。
  • fill_value用于填写缺失值。

前面代码的输出如下:

前面的数据透视表代表了那些body-style不同body-style和可用drive-wheels的汽车的平均价格。

  1. 我们还可以对不同的列应用不同的聚合函数:
table = pd.pivot_table(new_dataset1, values=['price','height','width'],
                       index =["body-style","drive-wheels"],
                       aggfunc={'price': np.mean,'height': [min, max],'width': [min, max]},
                       fill_value=0)
table

前面代码的输出如下:

这个数据透视表代表了指数中提到的各个类别的汽车的高度、宽度和平均价格的最大值和最小值。

交叉列表

我们可以用另一种叫做交叉制表的技术定制pandas数据框。这使我们能够应对groupby和聚合,以进行更好的数据分析。Pandas 有crosstab功能,这有助于建立交叉列表。交叉列表显示了某些数据组出现的频率。让我们来看看:

  1. 我们用pd.crosstab()来看看不同厂家生产了多少不同车身风格的车:
pd.crosstab(df["make"], df["body-style"])

前面代码的输出如下:

让我们应用边距和margins_name属性来显示交叉表的行和列总和,如以下代码所示:

# apply margins and margins_name attribute to displays the row wise 
# and column wise sum of the cross table
pd.crosstab(df["make"], df["body-style"],margins=True,margins_name="Total Made")

前面代码的输出如下:

在行索引或列索引或两者的crosstab函数中应用多列将自动打印分组输出。

  1. 让我们看看数据是如何在 T2 的汽车制造商和他们的门类型中按body-typedrive_wheels列分布的:
pd.crosstab([df["make"],df["num-of-doors"]], [df["body-style"],df["drive-wheels"]],
            margins=True,margins_name="Total Made")

前面代码的输出如下:

现在,让我们重命名列和行索引。重命名让我们更好地理解交叉列表,如下面的代码所示:

# rename the columns and row index for better understanding of crosstab
pd.crosstab([df["make"],df["num-of-doors"]], [df["body-style"],df["drive-wheels"]],
            rownames=['Auto Manufacturer', "Doors"],
            colnames=['Body Style', "Drive Type"],
            margins=True,margins_name="Total Made").head()

前面代码的输出如下:

这些是一些交叉列表的例子,给了我们数据在各自类别中的频率分布。

pd.crosstab的透视表语法也接受一些参数,如 dataframe 列、值、normalize 和聚合函数。我们可以同时将聚合函数应用于交叉表。传递聚合函数和值(聚合将应用于这些列)会为我们提供一个数据框汇总子集的交叉表。

  1. 首先,让我们通过对crosstable应用mean()聚合函数来查看不同制造商生产的汽车相对于其body-style的平均值curb-weight:
# values are the column in which aggregation function is to be applied
# aggfunc is the aggregation function to be applied
# round() to round the output

pd.crosstab(df["make"], df["body-style"],values=df["curb-weight"],
            aggfunc='mean').round(0)

前面代码的输出如下:

归一化的crosstab将显示每个组合出现的时间百分比。这可以使用normalize参数来完成,如下所示:

pd.crosstab(df["make"], df["body-style"],normalize=True).head(10)

前面代码的输出如下:

当我们试图分析两个或更多变量时,交叉列表技术会很方便。这有助于我们考察它们之间的关系。

摘要

将数据分组到相似的类别中是任何数据分析任务中必不可少的操作。在本章中,我们讨论了不同的分组技术,包括分组机制、重新排列、重塑数据结构、数据聚合方法和交叉制表方法。除此之外,我们还检查了每个案例的各种示例。

在下一章中,我们将学习相关性,它描述了两个或多个变量是如何相关的。除此之外,我们将通过合适的例子来看看不同类型的相关技术及其应用。

进一步阅读

  • Pandas 食谱:科学计算食谱使用 Python 进行时间序列分析和数据可视化第一版,作者:Theodore petroPACKT 出版物,2017
  • 掌握 Pandas-第二版,作者:阿希什·库马尔PACKT 出版,2019 年 10 月 25 日
  • 学习 Pandas-第二版,作者:迈克尔·海特PACKT 出版,2017 年 6 月 29 日

七、互相关

在本章中,我们将探讨不同因素之间的相关性,并估计这些不同因素的可靠程度。此外,我们将了解我们可以进行的不同类型的检查,以发现数据之间的关系,包括单变量分析、双变量分析和多变量分析。我们将使用泰坦尼克号数据集进行这些分析。我们还将介绍辛普森悖论。同样,我们将深入了解一个众所周知的事实:相关性并不意味着因果关系。

在本章中,我们将涵盖以下主题:

  • 引入相关性
  • 理解单变量分析
  • 理解二元分析
  • 理解多元分析
  • 使用泰坦尼克号数据集讨论多元分析
  • 概述辛普森悖论
  • 相关性并不意味着因果关系

技术要求

本章的代码可以在chapter 7文件夹内的 GitHub 资源库(https://GitHub . com/PacktPublishing/用 python 动手探索数据分析)中找到:

引入相关性

我们要分析的任何数据集都将有代表不同事实的多个观察值(即变量)的不同字段(即列)。数据集的列很可能是相互关联的,因为它们是从同一事件中收集的。记录的一个字段可能会也可能不会影响另一个字段的值。为了检查这些列之间的关系类型并分析它们之间的因果关系,我们必须努力找出变量之间存在的依赖关系。数据集的两个字段之间的这种关系的强度称为相关性,由-1 到 1 之间的数值表示。

换句话说,检验关系并解释变量对是否相互关联以及关联程度的统计技术被称为相关性。相关性回答了诸如一个变量相对于另一个变量如何变化的问题。如果它确实改变了,那么改变到什么程度或强度?此外,如果这些变量之间的关系足够强,那么我们可以对未来的行为做出预测。

比如身高和体重都有关系;也就是说,个子高的人往往比个子矮的人更重。如果我们有一个比我们之前观察到的平均身高更高的新人,那么他们的体重更有可能超过我们观察到的平均体重。

相关性告诉我们变量是如何一起变化的,既有相同的方向,也有相反的方向,还有关系的大小(即强度)。为了找到相关性,我们计算皮尔逊相关系数,用ρ(希腊字母ρ)表示。这是通过将协方差除以变量标准差的乘积得到的:

就关系的强度而言,两个变量 AB 之间的相关值在+1 和-1 之间变化。如果相关是+1,那么就说是完美的正/线性相关(即变量 A 与变量 B 成正比),而-1 的相关是完美的负相关(即变量 A 与变量 B 成反比)。请注意,接近 0 的值根本不应该相关。如果相关系数的绝对值接近 1,那么这些变量就被称为强相关;相比之下,那些接近 0.5 的被称为弱相关。

让我们看一些使用散点图的例子。散点图显示一个变量受另一个变量影响的程度:

如第一个和最后一个图表所示,当绘制成直线时,数据点之间的距离越近,相关变量之间的相关性越高。它们之间的相关性越高,变量之间的关系就越强。绘制时数据点越分散(因此没有模式),两个变量之间的相关性越低。在这里,您应该注意以下四个要点:

  • 当数据点图有一条直线穿过原点到达 xy 值时,则称变量间有正相关
  • 当数据点绘制成从 y 轴上的高值到 x 轴上的高值的线时,变量被称为具有负相关
  • 完美相关性的值为 1。
  • 完美负相关的值为-1。

高度正相关的值接近 1。高度负相关的值接近-1。在上图中,+0.8 表示高度正相关,-0.8 表示高度负相关。数字越接近 0(在图中,这是+0.3 和-0.3),相关性越弱。

在分析数据集中的相关性之前,让我们了解各种类型的分析。

分析类型

在本节中,我们将探讨不同类型的分析。我们将从单变量分析开始,然后进行双变量分析,最后,我们将讨论多变量分析。

理解单变量分析

还记得我们在第五章描述性统计中使用的描述性统计的测量变量吗?我们有一组从 2 到 12 的整数。我们计算了该集合的平均值、中值和模式,并分析了整数的分布模式。然后,我们计算了每种类型汽车数据集的高度列中可用值的平均值、模式、中值和标准偏差。对单一类型数据集的这种分析称为单变量分析

单变量分析是分析数据的最简单形式。这意味着我们的数据只有一种类型的变量,我们对它进行分析。单变量分析的主要目的是获取数据,总结数据,并在值之间找到模式。它不涉及原因或价值观之间的关系。描述单变量数据中发现的模式的几种技术包括中心趋势(即平均值、模式和中值)和离差(即范围、方差、最大和最小四分位数(包括四分位数之间的范围)和标准偏差)。

你为什么不试着对同一组数据再做一次分析呢?这一次,记住这是单变量分析:

  1. 首先导入所需的库并加载数据集:
#import libraries
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
  1. 现在,加载数据:
# loading dataset as Pandas dataframe
df = pd.read_csv("data.csv")
df.head()

该代码的输出如下所示:

  1. 首先,检查每列的数据类型。现在,您必须熟悉以下内容:
df.dtypes

输出如下:

symboling int64
normalized-losses int64
make object
aspiration object
num-of-doors object
body-style object
drive-wheels object
engine-location object
wheel-base float64
length float64
width float64
height float64
curb-weight int64
engine-type object
num-of-cylinders object
engine-size int64
fuel-system object
bore float64
stroke float64
compression-ratio float64
horsepower float64
peak-rpm float64
city-mpg int64
highway-mpg int64
price float64
city-L/100km float64
horsepower-binned object
diesel int64
gas int64
dtype: object
  1. 现在计算高度柱中心趋势的度量。回想一下,我们在第 5 章描述性统计:中讨论了几个描述性统计
#calculate mean, median and mode of dat set height
mean = df["height"].mean()
median =df["height"].median()
mode = df["height"].mode()
print(mean , median, mode)

这些描述性函数的输出如下:

53.766666666666715 54.1 0 50.8
dtype: float64
  1. 现在,让我们在图表中可视化这个分析:
#distribution plot
sns.FacetGrid(df,size=5).map(sns.distplot,"height").add_legend()

该代码将在高度列中生成值的分布图:

从图表中,我们可以观察到最大汽车的最大高度在 53 到 57 之间。现在,让我们对价格列进行同样的操作:

#distribution plot
sns.FacetGrid(df,size=5).map(sns.distplot,"price").add_legend()

该代码的输出如下所示:

看这张图,可以说价格在 5000 到 45000 之间,但是最高车价在 5000 到 10000 之间。

箱线图也是直观表示单变量分析中的中位数和四分位数等统计指标的有效方法:

#boxplot for price of cars
sns.boxplot(x="price",data=df)
plt.show()

根据前面的代码生成的方框图如下所示:

方框的右边框是 Q3,也就是第三个四分位数,方框的左边框是 Q1,也就是第一个四分位数。线条从方框边界的两侧向最小值和最大值延伸。基于我们的绘图工具使用的惯例,虽然,他们可能只扩展到某个统计数据;超出这些统计数据的任何值都被标记为异常值(使用点)。

该分析针对的是仅包含单一类型变量的数据集。现在,让我们看一下下一种对具有两种类型变量的数据集的分析形式,称为二元分析。

理解二元分析

顾名思义,这是对一个以上(也就是正好两个)类型变量的分析。双变量分析用于找出两个不同变量之间是否存在关系。当我们在笛卡尔平面上绘制一个变量与另一个变量的散点图时(想想 xy 轴),它给了我们一幅数据试图告诉我们的画面。如果数据点似乎符合直线或曲线,则两个变量之间存在关系或相关性。一般来说,如果我们知道自变量的值,二元分析有助于我们预测一个变量(即因变量)的值。

这是一张图表,显示了一段时间内广告费用和销售费率的散点图:

这个图是二元分析的散点图,其中销售额广告费是两个变量。绘制散点图时,我们可以看到销售价值取决于广告费用;也就是说,随着广告费的增加,销售价值也随之增加。对两个变量之间关系的这种理解将指导我们未来的预测:

  1. 现在是时候对我们的汽车数据集进行二元分析了。让我们看看马力是否是汽车定价的一个依赖因素:
# plot the relationship between “horsepower” and ”price”
plt.scatter(df["price"], df["horsepower"])
plt.title("Scatter Plot for horsepower vs price")
plt.xlabel("horsepower")
plt.ylabel("price")

该代码将生成散点图,其 y 轴上的price范围和 x 轴上的horsepower值如下:

正如你在上图中看到的,汽车的马力是价格的一个依赖因素。随着汽车马力的增加,汽车的价格也随之增加。

方框图也是查看一些统计度量以及两个值之间关系的好方法。

  1. 现在,让我们在汽车的发动机位置和价格之间画一个方框图:
#boxplot
sns.boxplot(x="engine-location",y="price",data=df)
plt.show()

该代码将生成一个方框图,价格范围在 y 轴,发动机位置类型在 x 轴:

该图显示,前置发动机位置为的汽车价格比后置发动机位置为的汽车价格低得多。此外,还有一些异常值具有前置引擎位置,但价格要高得多。****

  1. 接下来,用价格范围和驱动轮类型绘制另一个方框图:
#boxplot to visualize the distribution of "price" with types of "drive-wheels"
sns.boxplot(x="drive-wheels", y="price",data=df)

该代码的输出如下所示:

这张图表显示了不同车轮类型汽车的价格范围。这里,方框图显示了各个车轮类型的平均和中间价格以及一些异常值。

这是一个简短的介绍,以及一些二元分析的实践例子。现在,让我们了解一种更有效的数据分析实践,多元分析。

理解多元分析

多元分析是对三个或更多变量的分析。这使我们能够观察相关性(即一个变量相对于另一个变量如何变化),并试图比二元分析更准确地预测未来行为。

最初,我们探索了单变量分析和双变量分析的可视化;同样,让我们把多元分析的概念形象化。

绘制多变量数据的一种常见方法是绘制矩阵散点图,称为对图。矩阵图或成对图显示了每对变量之间的互相关。配对图让我们可以看到单个变量的分布和两个变量之间的关系:

  1. 我们可以使用pandas.tools.plotting包中的scatter_matrix()功能或seaborn包中的seaborn.pairplot()功能来完成此操作:
# pair plot with plot type regression
sns.pairplot(df,vars = ['normalized-losses', 'price','horsepower'], kind="reg")
plt.show()

该代码将绘制一个 3×3 矩阵,其中包含标准化损失、价格和马力列中的不同数据:

如前图所示,对角线上的直方图允许我们说明单个变量的分布。上三角形和下三角形上的回归图展示了两个变量之间的关系。第一行中间的图为回归图;这表明正常损失和汽车价格之间没有相关性。相比之下,最下面一排中间的回归图说明了价格和马力之间有着巨大的相关性。

  1. 同样,我们可以通过指定颜色、标签、绘图类型、对角线绘图类型和变量,使用成对绘图进行多元分析。那么,让我们制作另一个配对图:
#pair plot (matrix scatterplot) of few columns 
sns.set(style="ticks", color_codes=True)
sns.pairplot(df,vars = ['symboling', 'normalized-losses','wheel-base'], hue="drive-wheels")
plt.show()

该代码的输出如下所示:

这是一对符号化、标准化损失、轮基和驱动轮柱的记录图。

对角线上的密度图允许我们看到单个变量的分布,而上下三角形上的散点图显示了两个变量之间的关系(或相关性)。色调参数是用于数据点标签的列名;在此图中,驱动轮类型用颜色标注。第二行最左边的图显示了归一化损失与轴距的散点图。

如前所述,相关性分析是发现多元数据集中的任何变量是否相关的有效技术。要计算一对变量的线性(皮尔逊)相关系数,我们可以使用pandas包中的dataframe.corr(method ='pearson')函数和scipy.stats包中的pearsonr()函数:

  1. 例如,要计算价格和马力的相关系数,请使用以下公式:
from scipy import stats
corr = stats.pearsonr(df["price"], df["horsepower"])
print("p-value:\t", corr[1])
print("cor:\t\t", corr[0])

输出如下:

p-value: 6.369057428260101e-48 
cor: 0.8095745670036559

这里这两个变量的相关性为0.80957,接近+1。因此,我们可以确保价格和马力高度正相关。

  1. 使用 pandas corr(函数,整个数值记录之间的相关性可以计算如下:
correlation = df.corr(method='pearson')
correlation

该代码的输出如下所示:

  1. 现在,让我们使用热图来可视化这种相关性分析。热图是让它看起来更漂亮、更容易理解的最佳技术:
sns.heatmap(correlation,xticklabels=correlation.columns,
            yticklabels=correlation.columns)

该代码的输出如下所示:

系数接近 1 意味着这两个变量之间有很强的正相关。对角线是变量与其自身的相关性,所以它们当然是 1。

这是一个简单的介绍,以及一些多元分析的实践例子。现在,让我们用流行的数据集泰坦尼克号来练习它们,泰坦尼克号在世界各地经常被用来练习数据分析和机器学习算法。数据来源在本章技术要求部分有提及。

使用泰坦尼克号数据集讨论多元分析

1912 年 4 月 15 日,当时制造的最大客轮在处女航时与冰山相撞。泰坦尼克号沉没时,2224 名乘客和船员中有 1502 人遇难。titanic.csv(https://web . Stanford . edu/class/archive/cs/cs109/cs109.1166/stuff/Titanic . CSV)文件中包含了 887 名真实泰坦尼克号乘客的数据。每行代表一个人。这些列描述了船上人员的不同属性,其中PassengerId列是乘客的唯一 ID,Survived是幸存(1)或死亡(0)的数字,Pclass是乘客的级别(即第一、第二或第三),Name是乘客的姓名,Sex是乘客的性别,Age是乘客的年龄,Siblings/Spouses Aboard是泰坦尼克号上的兄弟姐妹/配偶的数量,Parents/Children Aboard是泰坦尼克号上的父母/子女的数量,TicketFare是每张票的票价,Cabin是舱号,Embarked是乘客上船的地点(例如:C 指瑟堡,S 指南安普顿,Q 指皇后镇)。

让我们分析一下泰坦尼克号的数据集,找出那些对乘客生存有最大依赖性的属性:

  1. 首先加载数据集和所需的库:
# load python libraries
import numpy as np 
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
#load dataset
titanic=pd.read_csv("/content/titanic.csv")
titanic.head()

该代码的输出如下所示:

让我们看看代码中数据框的形状:

titanic.shape

输出如下:

(891, 12)
  1. 让我们看一下数据集中丢失的记录数:
total = titanic.isnull().sum().sort_values(ascending=False)
total

输出如下:

Cabin 687
Age 177
Embarked 2
Fare 0
Ticket 0
Parch 0
SibSp 0
Sex 0
Name 0
Pclass 0
Survived 0
PassengerId 0
dtype: int64

除了EmbarkedAgeCabin之外,所有记录似乎都很好。Cabin功能需要进一步研究才能填充这么多,但我们不要在分析中使用它,因为它缺少 77%。此外,处理Age功能会相当棘手,它有 177 个缺失值。我们不能忽视年龄因素,因为它可能与存活率相关。Embarked功能只有两个缺失值,可以轻松填充。

由于PassengerIdTicketName列具有唯一的值,因此它们与高存活率不相关。

  1. 首先,让我们找出从灾难中幸存下来的女性和男性的百分比:
#percentage of women survived
women = titanic.loc[titanic.Sex == 'female']["Survived"]
rate_women = sum(women)/len(women)

#percentage of men survived
men = titanic.loc[titanic.Sex == 'male']["Survived"]
rate_men = sum(men)/len(men)

print(str(rate_women) +" % of women who survived." )
print(str(rate_men) + " % of men who survived." )

输出如下:

0.7420382165605095 % of women who survived.
0.18890814558058924 % of men who survived.
  1. 在这里,你可以看到幸存的女性数量很高,所以性别可能是一个有助于分析任何变量(人)存活率的属性。让我们用男性和女性的存活数来形象化这个信息:
titanic['Survived'] = titanic['Survived'].map({0:"not_survived", 1:"survived"})

fig, ax = plt.subplots(1, 2, figsize = (10, 8))
titanic["Sex"].value_counts().plot.bar(color = "skyblue", ax = ax[0])
ax[0].set_title("Number Of Passengers By Sex")
ax[0].set_ylabel("Population")
sns.countplot("Sex", hue = "Survived", data = titanic, ax = ax[1])
ax[1].set_title("Sex: Survived vs Dead")
plt.show()

该代码的输出如下所示:

  1. 让我们想象一下不同类别的幸存者和死亡人数:
fig, ax = plt.subplots(1, 2, figsize = (10, 8))
titanic["Pclass"].value_counts().plot.bar(color = "skyblue", ax = ax[0])
ax[0].set_title("Number Of Passengers By Pclass")
ax[0].set_ylabel("Population")
sns.countplot("Pclass", hue = "Survived", data = titanic, ax = ax[1])
ax[1].set_title("Pclass: Survived vs Dead")
plt.show()

该代码的输出如下所示:

  1. 嗯,看起来Pclass的乘客数量很高,大部分都活不下去。在Pclass 2 中,死亡人数高。并且,PclassT7】1 显示了幸存乘客的最大数量:
fig, ax = plt.subplots(1, 2, figsize = (10, 8))
titanic["Embarked"].value_counts().plot.bar(color = "skyblue", ax = ax[0])
ax[0].set_title("Number Of Passengers By Embarked")
ax[0].set_ylabel("Number")
sns.countplot("Embarked", hue = "Survived", data = titanic, ax = ax[1])
ax[1].set_title("Embarked: Survived vs Unsurvived")
plt.show()

代码的输出如下所示:

大多数乘客似乎是从南安普敦上船的,其中近 450 人没有生还。

  1. 为了可视化Age记录,我们将使用distplot()方法绘制数据分布。正如我们之前分析的那样,Age记录中有 177 个空值,因此我们将在绘制数据之前删除它们:
sns.distplot(titanic['Age'].dropna())

该代码的输出如下所示:

  1. 现在,让我们首先使用SurvivedPclassFearAge变量对泰坦尼克号数据集进行多元分析:
sns.set(style="ticks", color_codes=True)
sns.pairplot(titanic,vars = [ 'Fare','Age','Pclass'], hue="Survived")
plt.show()

该代码的输出如下所示:

  1. 现在,让我们用热图查看相关表。请注意,第一个Embarked映射记录带有整数值,因此我们也可以将Embarked包括在我们的相关性分析中:
titanic['Embarked'] = titanic['Embarked'].map({"S":1, "C":2,"Q":2,"NaN":0})
Tcorrelation = titanic.corr(method='pearson')
Tcorrelation

该代码的输出如下所示:

  1. 结果很简单。它显示了各个列之间的相关性。如您所见,在该表中,PassengerIdFareAge列呈弱正相关关系:
sns.heatmap(Tcorrelation,xticklabels=Tcorrelation.columns,
            yticklabels=Tcorrelation.columns)

该代码的输出如下所示:

如果你想练习更多的分析和预测算法,你可以在 Kaggle 中获得相同的数据集。

到目前为止,您已经了解了数据分析的相关性和类型。您也对数据集进行了不同的分析。现在,我们需要仔细考虑事实,然后根据我们获得的输出对分析做出任何结论。

概述辛普森悖论

通常,我们根据数据集做出的决策会受到我们应用于它们的统计度量的输出的影响。这些输出告诉我们相关的类型和数据集的基本可视化。然而,有时,当我们将数据分组并应用统计度量时,或者当我们将数据聚合在一起并应用统计度量时,决策会有所不同。同一数据集结果中的这种异常行为一般称为辛普森悖论。简而言之,辛普森悖论是在两种不同情况下分析数据集时出现在分析趋势中的差异:第一,当数据被分成组时,第二,当数据被聚合时。

这里有一个表格,分别代表了男性和女性对两种不同游戏主机的推荐率,也包括:

| | 推荐****PS4 | 推荐****Xbox One |
| 男性的 | 50/150=30% | 180/360=50% |
| 女性的 | 200/250=80% | 36/40=90% |
| 联合的;共同的 | 250/400=62.5% | 216/400=54% |

上表按男性和女性分别列出了 PS4 和 Xbox One 两款不同游戏机的推荐率,包括单独推荐和组合推荐。

假设你打算买一个有最大推荐的游戏机。如上表所示,Xbox One 的男女推荐比例均高于 PS4。但是,使用相同的数据组合时,根据所有用户,PS4 具有更高的推荐百分比(62.5%)。那么,你怎么决定选哪一个呢?这些计算看起来不错,但从逻辑上讲,决策似乎并不顺利。这就是辛普森悖论。这里的同一个数据集证明了两个相反的论点。

在这种情况下,主要问题是,当我们只看到单独数据的百分比时,它没有考虑样本量。由于每个分数都显示了通过询问的数量推荐游戏控制台的用户数量,因此考虑整个样本大小是有意义的。男性和女性单独数据中的样本量相差很大。比如男性对 PS4 的推荐是 50,而对 Xbox One 的推荐是 180。这些数字有着巨大的差异。Xbox One 的男性响应远远多于女性,而 PS4 的情况正好相反。因为推荐 PlayStation 的男性更少,这导致数据合并后 PS4 的平均评分更低,这导致了矛盾。

为了就我们应该使用哪个控制台做出单一决定,我们需要决定数据是可以合并还是应该单独查看。在这种情况下,我们必须找出哪个控制台最有可能同时满足男性和女性。可能还有其他因素影响这些评论,但我们没有这些数据,所以我们寻找最大数量的好评论,而不考虑性别偏见。在这里,聚合数据最有意义。我们将结合评审结果,与总体平均水平进行比较。因为我们的目标是合并评论并查看总平均值,所以数据的汇总更有意义。

看起来辛普森悖论是一个牵强的问题,理论上是可能的,但实际上从未发生过,因为我们对整体可用数据的统计分析是准确的。然而,现实世界中有许多关于辛普森悖论的著名研究。

现实世界的一个例子是精神健康治疗,如抑郁症。下表列出了两种治疗方法对患者的疗效:

| | 疗法 A | 疗法 B |
| 轻度萧条 | 81/87=93% | 234/270=87% |
| 严重抑郁 | 192/263=73% | 55/80=69% |
| 两者 | 273/350=78% | 289/350=83% |

如你所见,在上表中,有两种疗法: T 疗法 A疗法 B 。治疗 A 似乎对轻度和重度抑郁症都更有效,但汇总数据显示,治疗 B 对两种情况都更有效。这怎么可能?嗯,我们不能断定数据汇总后的结果是正确的,因为每种治疗方法的样本量是不同的。为了就我们应该采用哪种疗法做出一个单一的决定,我们需要实事求是地思考:数据是如何产生的?还有哪些因素影响了根本看不到的结果?

现实中,轻度抑郁被医生认为是不太严重的情况,A 疗法比 b 疗法更便宜,因此,医生建议轻度抑郁采用更简单的 A 疗法。

我们的数据集中没有提到这两种治疗类型的细节和事实。抑郁症的种类和病情的严重程度会导致混杂变量(混杂变量是我们在数据表中看不到的,但它们可以通过对数据的背景分析来确定),因为它会影响选择何种治疗和康复方法的决定。因此,决定哪种治疗对患者更有效的因素取决于混杂变量,这就是这里情况的严重性。为了确定哪种疗法效果更好,我们需要病例严重性的报告,然后需要比较两种疗法的恢复率,而不是跨组的汇总数据。

从一组数据中回答我们想要的问题有时需要更多的分析,而不仅仅是查看可用的数据。从辛普森悖论中得到的教训是,光有数据是不够的。数据从来都不是纯粹客观的,最终的剧情也不是。因此,在处理一组数据时,我们必须考虑我们是否得到了整个故事。

在根据我们得到的输出结论分析之前,必须考虑的另一个事实是相关性并不意味着因果关系。这在统计领域非常重要,所以维基百科有一篇单独的文章介绍了这一说法。

相关性并不意味着因果关系

C 或关联 并不意味着因果关系是一个有趣的短语,你会在统计学和详细学习数据科学时听到它。但这意味着什么呢?嗯,这只是表明,仅仅因为两件事相关,并不总是意味着一个导致另一个。例如,挪威的冬天很冷,人们往往比夏天花更多的钱购买汤等热食。然而,这并不意味着寒冷的天气会导致人们在汤上花更多的钱。因此,虽然挪威人的支出与天气寒冷有关,但支出并不是天气寒冷的原因。因此,相关性不是因果关系。

请注意,在这个短语中有两个基本术语:相关性和因果关系。相关性揭示了一对变量之间的相互关联和共同变化的程度。因果关系解释说,一个变量的值的任何变化都会导致另一个变量的量的差异。在这种情况下,一个变量使另一个变量发生。这种现象被称为因果。比如你运动的时候( X ),你燃烧的热量( Y )每分钟都比较高。因此, X 导致 Y 。根据逻辑理论,我们可以这样说:

任何数据分析书中最常见的例子都是关于冰淇淋的销售和凶杀案的兴衰。根据这个例子,随着冰淇淋销量的增加,凶杀案的数量也在增加。基于相关性,这两个事件是高度相关的。然而,冰淇淋的消费并没有导致人的死亡。这两件事不是基于因果理论。因此,相关性并不意味着因果关系。

那么,这个关键短语的要点是什么?嗯,首先,我们不应该根据相关性太快地形成我们的结论。为了理解任何关键的、隐藏的因素,有必要投入一些时间来寻找数据的潜在因素。

摘要

在本章中,我们讨论了相关性。相关性是一种统计度量,可用于检查一对变量之间的关系。理解这些关系可以帮助你从一组变量中决定最重要的特征。一旦我们理解了相关性,我们就可以用它来做出更好的预测。变量之间的关系越高,预测的准确性越高。由于相关性更重要,在本章中,我们已经介绍了几种相关方法和不同类型的分析,包括单变量分析、双变量分析和多变量分析。

在下一章中,我们将仔细研究时间序列分析。我们将使用几个真实的数据库,包括时间序列分析,以便进行探索性的数据分析。

进一步阅读

  • 关联和相关性,作者:李·贝克帕克特出版,2019 年 6 月 28 日
  • Python 数据科学,作者:罗汉·乔普拉艾伦·英伦穆罕默德·努尔登·阿劳登帕克特出版,2019 年 7 月
  • 与 NumPy 和 Pandas 的实践数据分析,作者:柯蒂斯·米勒帕克特出版,2018 年 6 月**

八、时间序列分析

时间序列数据包括时间戳,通常是在监控工业过程或跟踪任何业务指标时生成的。等间隔的时间戳值的有序序列被称为时间序列。对这种时间序列的分析被用于许多应用,例如销售预测、效用研究、预算分析、经济预测、库存研究等等。有太多的方法可以用来建模和预测时间序列。

在本章中,我们将使用 Python 库来探索时间序列分析 ( TSA )。时间序列数据是关于一个系统或过程的一系列定量观察的形式,是在连续的时间点上产生的。

在本章中,我们将涵盖以下主题:

  • 了解时间序列数据集
  • 开放电力系统数据的美国运输安全管理局

技术要求

本章使用的所有代码和数据集都可以在 GitHub 存储库中找到(https://GitHub . com/PacktPublishing/用 python 动手探索数据分析):

  • 代码:本章需要的代码可以在标有Chapter 8/的文件夹中找到。
  • 数据集:我们将为美国运输安全管理局使用开放电力系统数据。可以从https://open-power-system-data.org/下载。也可以在Chapter 9/datasets里面的 GitHub 资源库里面找到数据集。

了解时间序列数据集

最基本的问题是,我们所说的时间序列数据是什么意思?当然,我们已经听过好几次了。或许我们可以给它下定义?当然可以。本质上,时间序列是按时间顺序进行的观察的集合。请注意,这里有两个重要的关键短语— 一组观察数据按时间顺序排列。因为它是一个系列,它必须是一个观察的集合,因为它处理时间,它必须以顺序的方式处理它。

让我们以时间序列数据为例:

上图为 2016 年前六个月的太阳能产量(单位为千兆瓦 小时 ( 千兆瓦)。它还显示了每日和每周的用电量。

运输安全协议基础

为了理解时间序列数据集,让我们随机生成一个规范化的数据集:

  1. 我们可以使用numpy库生成数据集:
import os
import numpy as np
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns

zero_mean_series = np.random.normal(loc=0.0, scale=1., size=50)
zero_mean_series

我们使用了 NumPy 库来生成随机数据集。因此,这里给出的输出对您来说会有所不同。这里给出了前面代码的输出:

array([-0.73140395, -2.4944216 , -1.44929237, -0.40077112,  0.23713083, 0.89632516, -0.90228469, -0.96464949, 1.48135275,  0.64530002, -1.70897785,  0.54863901, -1.14941457, -1.49177657, -2.04298133, 1.40936481,  0.65621356, -0.37571958, -0.04877503, -0.84619236, -1.46231312,  2.42031845, -0.91949491,  0.80903063,  0.67885337, -0.1082256 , -0.16953567,  0.93628661,  2.57639376, -0.01489153, 0.9011697 , -0.29900988,  0.04519547,  0.71230853, -0.00626227, 1.27565662, -0.42432848,  1.44748288,  0.29585819,  0.70547011, -0.6838063 ,  1.61502839, -0.04388889,  1.06261716,  0.17708138, 0.3723592 , -0.77185183, -3.3487284 ,  0.59464475, -0.89005505])
  1. 接下来,我们将使用seaborn库绘制时间序列数据。检查这里给出的代码片段:
plt.figure(figsize=(16, 8))
g = sns.lineplot(data=zero_mean_series)
g.set_title('Zero mean model')
g.set_xlabel('Time index')
plt.show()

我们使用seaborn库提供的内置方法seaborn.lineplot()函数绘制了时间序列图。这里给出了前面代码的输出:

  1. 我们可以对列表执行累计求和,然后使用时间序列图绘制数据。剧情给出了更有趣的结果。检查以下代码片段:
random_walk = np.cumsum(zero_mean_series)
random_walk

它生成一个累积和的数组,如下所示:

array([ -0.73140395,  -3.22582556,  -4.67511792,  -5.07588904,-4.83875821,  -3.94243305,  -4.84471774,  -5.80936723,-4.32801448,  -3.68271446,  -5.39169231,  -4.8430533 ,-5.99246787,  -7.48424444,  -9.52722576,  -8.11786095,-7.46164739,  -7.83736697,  -7.886142  ,  -8.73233436, -10.19464748,  -7.77432903,  -8.69382394,  -7.88479331,-7.20593994,  -7.31416554,  -7.4837012 ,  -6.5474146 ,-3.97102084,  -3.98591237,  -3.08474267,  -3.38375255,-3.33855708,  -2.62624855,  -2.63251082,  -1.35685419,-1.78118268,  -0.3336998 ,  -0.03784161,   0.66762849,-0.01617781,   1.59885058,   1.55496169,   2.61757885, 2.79466023,   3.16701943,   2.3951676 ,  -0.9535608 ,-0.35891606,  -1.2489711 ])

请注意,对于任何特定值,下一个值是以前值的总和。

  1. 现在,如果我们使用这里所示的时间序列图绘制列表,我们会得到一个有趣的图表,显示值随时间的变化:
plt.figure(figsize=(16, 8))
g = sns.lineplot(data=random_walk)
g.set_title('Random Walk')
g.set_xlabel('Time index')
plt.show()

这里给出了前面代码的输出:

请注意上图中显示的图表。它显示了值随时间的变化。太好了——到目前为止,我们已经生成了不同的时间序列数据,并使用内置的seaborn.tsplot()方法绘制出来。

单变量时间序列

当我们捕捉到同一变量在特定持续时间内的一系列观测值时,该序列被称为单变量时间序列。一般来说,在单变量时间序列中,观测是在规则的时间段内进行的,例如一天中温度随时间的变化。

时间序列数据的特征

使用时间序列数据时,可以观察到几个独特的特征。一般来说,时间序列往往表现出以下特征:

  • 看时间序列数据的时候,看有没有趋势是必不可少的。观察趋势意味着平均测量值似乎随着时间的推移而减少或增加。
  • 时间序列数据可能包含大量异常值。当绘制在图表上时,可以注意到这些异常值。
  • 时间序列中的一些数据往往会在一定的时间间隔内以某种模式重复出现。我们称这样的重复模式为季节性
  • 有时,时间序列数据会出现不均匀的变化。我们把这种不均匀的变化称为突变。观察时间序列中的突变是至关重要的,因为它揭示了基本的潜在现象。
  • 随着时间的推移,一些序列趋向于遵循恒定方差。因此,有必要查看时间序列数据,看看数据是否随时间呈现恒定的变化。

前面列出的特征有助于我们对运输安全管理局进行更好的分析。既然我们知道了在时间序列数据中应该看到什么和期望什么,那么看到一些实际的例子将会很有用。接下来,让我们导入一个真实的数据库,并对其执行各种 TSA 方法。

开放电力系统数据的美国运输安全管理局

在本节中,我们将使用开放电力系统数据来了解 TSA。我们将研究时间序列数据结构、基于时间的索引以及可视化时间序列数据的几种方法。

我们将从导入数据集开始。看看这里给出的代码片段:

# load time series dataset
df_power = pd.read_csv("https://raw.githubusercontent.com/jenfly/opsd/master/opsd_germany_daily.csv")
df_power.columns

这里给出了前面代码的输出:

Index(['Consumption', 'Wind', 'Solar', 'Wind+Solar'], dtype='object')

这里描述了数据框的列:

  • 日期:日期格式为yyyy-mm-dd
  • 消耗:表示GWh用电量。
  • 太阳能:表示GWh太阳能发电。
  • 风能+太阳能:这代表GWh的太阳能和风能发电量之和。

请注意日期列,它包含时间序列数据集。我们可以使用这个数据集来发现德国的电力消费和生产是如何随着时间的推移而变化的。

数据清理

现在让我们清除数据集的异常值:

  1. 我们可以从检查数据集的形状开始:
df_power.shape

这里给出了前面代码的输出:

(4383, 5)

dataframe 包含 4,283 行和 5 列。

  1. 我们还可以检查数据框中的一些条目。让我们检查最后 10 个条目:
df_power.tail(10)

这里给出了前面代码的输出:

  1. 接下来,让我们回顾一下df_power数据框中每一列的数据类型:
df_power.dtypes

这里给出了前面代码的输出:

Date object
Consumption float64
Wind float64
Solar float64
Wind+Solar float64
dtype: object
  1. 注意Date列的数据类型为object。这是不正确的。所以,下一步是修正Date一栏,如下图所示:
#convert object to datetime format
df_power['Date'] = pd.to_datetime(df_power['Date'])
  1. 它应该将Date列转换为Datetime格式。我们可以再次验证这一点:
df_power.dtypes

这里给出了前面代码的输出:

Date datetime64[ns]
Consumption float64
Wind float64
Solar float64
Wind+Solar float64
dtype: object

请注意,Date列已更改为正确的数据类型。

  1. 接下来,让我们将数据框的索引更改为Date列:
df_power = df_power.set_index('Date')
df_power.tail(3)

这里给出了前面代码的输出:

从前面的截图中注意到Date列已经被设置为DatetimeIndex

  1. 我们可以使用下面给出的代码片段来验证这一点:
df_power.index

这里给出了前面代码的输出:

DatetimeIndex(['2006-01-01', '2006-01-02', '2006-01-03', '2006-01-04', '2006-01-05', '2006-01-06', '2006-01-07', '2006-01-08', '2006-01-09', '2006-01-10', ... '2017-12-22', '2017-12-23', '2017-12-24', '2017-12-25', '2017-12-26', '2017-12-27', '2017-12-28', '2017-12-29', '2017-12-30', '2017-12-31'],dtype='datetime64[ns]', name='Date', length=4383, freq=None)
  1. 由于我们的索引是DatetimeIndex对象,现在我们可以使用它来分析数据帧。让我们在数据框架中添加更多的列,让我们的生活更轻松。让我们添加YearMonthWeekday Name:
# Add columns with year, month, and weekday name
df_power['Year'] = df_power.index.year
df_power['Month'] = df_power.index.month
df_power['Weekday Name'] = df_power.index.weekday_name
  1. 让我们显示数据框中的五个随机行:
# Display a random sampling of 5 rows
df_power.sample(5, random_state=0)

此代码的输出如下所示:

请注意,我们又添加了三列— YearMonthWeekday Name。添加这些列有助于简化数据分析。

基于时间的索引

对于时间序列数据,基于时间的索引是pandas库非常强大的方法。具有基于时间的索引允许使用格式化的字符串来选择数据。例如,请参见下面的代码:

df_power.loc['2015-10-02']

这里给出了前面代码的输出:

Consumption 1391.05
Wind 81.229
Solar 160.641
Wind+Solar 241.87
Year 2015
Month 10
Weekday Name Friday
Name: 2015-10-02 00:00:00, dtype: object

请注意,我们使用了 Pandas 数据框loc访问器。在前面的示例中,我们使用日期作为字符串来选择一行。我们可以使用各种技术来访问行,就像我们可以使用普通的 dataframe 索引一样。

可视化时间序列

让我们可视化时间序列数据集。我们将继续使用相同的df_power数据帧:

  1. 第一步是导入seabornmatplotlib库:
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(rc={'figure.figsize':(11, 4)})
plt.rcParams['figure.figsize'] = (8,5)
plt.rcParams['figure.dpi'] = 150
  1. 接下来,让我们生成德国每日用电量的完整时间序列的线图:
df_power['Consumption'].plot(linewidth=0.5)

这里给出了前面代码的输出:

如前一张截图所示,y 轴显示的是用电量,x 轴显示的是年份。然而,数据集太多,无法涵盖所有年份。

  1. 让我们用点来绘制所有其他列的数据:
cols_to_plot = ['Consumption', 'Solar', 'Wind']
axes = df_power[cols_to_plot].plot(marker='.', alpha=0.5, linestyle='None',figsize=(14, 6), subplots=True)
for ax in axes:
    ax.set_ylabel('Daily Totals (GWh)')

这里给出了前面代码的输出:

输出显示,电力消耗可以分为两种不同的模式:

  • 一个大约 1400 千兆瓦时及以上的集群
  • 另一个大约低于 1400 千兆瓦时的星团

此外,太阳能产量夏季较高,冬季较低。这些年来,风力发电的产量似乎有一个强劲的增长趋势。

  1. 我们可以进一步调查一年,以便更仔细地观察。检查此处给出的代码:
ax = df_power.loc['2016', 'Consumption'].plot()
ax.set_ylabel('Daily Consumption (GWh)');

这里给出了前面代码的输出:

从前面的截图中,我们可以清楚地看到 2016 年的用电量。该图显示了年底(12 月)和 8 月期间的用电量大幅下降。我们可以在任何特定的月份寻找进一步的细节。让我们用下面的代码块来检查 2016 年 12 月:

ax = df_power.loc['2016-12', 'Consumption'].plot(marker='o', linestyle='-')
ax.set_ylabel('Daily Consumption (GWh)');

这里给出了前面代码的输出:

如上图所示,工作日用电量较高,周末最低。我们可以看到一个月中每天的消耗量。我们可以进一步放大观察 12 月最后一周的消费情况。

为了指示 12 月的特定一周,我们可以提供如下所示的特定日期范围:

ax = df_power.loc['2016-12-23':'2016-12-30', 'Consumption'].plot(marker='o', linestyle='-')
ax.set_ylabel('Daily Consumption (GWh)');

如前面的代码所示,我们希望看到2016-12-232016-12-30之间的用电量。这里给出了前面代码的输出:

如前一张截图所示,圣诞节当天用电量最低,可能是因为人们忙于聚会。圣诞节后,消费增加。

将时间序列数据分组

我们可以按不同的时间段对数据进行分组,并以方框图的形式呈现出来:

  1. 我们可以先将数据按月分组,然后使用箱线图来可视化数据:
fig, axes = plt.subplots(3, 1, figsize=(8, 7), sharex=True)
for name, ax in zip(['Consumption', 'Solar', 'Wind'], axes):
  sns.boxplot(data=df_power, x='Month', y=name, ax=ax)
  ax.set_ylabel('GWh')
  ax.set_title(name)
  if ax != axes[-1]:
    ax.set_xlabel('') 

这里给出了前面代码的输出:

上图说明了用电量一般冬季较高,夏季较低。夏季风力较高。此外,还有许多与电力消耗、风力发电和太阳能发电相关的异常值。

  1. 接下来,我们可以按一周中的某一天对耗电量进行分组,并以方框图的形式呈现出来:
sns.boxplot(data=df_power, x='Weekday Name', y='Consumption');

这里给出了前面代码的输出:

上图截图显示工作日用电量高于周末。有趣的是,工作日的异常值更多。

重新采样时间序列数据

通常需要以较低或较高的频率对数据集进行重新采样。这种重采样是基于聚合或分组操作完成的。例如,我们可以基于周平均时间序列对数据进行重新采样,如下所示:

  1. 我们可以使用这里给出的代码对数据进行重新采样:
columns = ['Consumption', 'Wind', 'Solar', 'Wind+Solar']

power_weekly_mean = df_power[columns].resample('W').mean()
power_weekly_mean

这里给出了前面代码的输出:

如前一张截图所示,第一行标记为2006-01-01,包含所有数据的平均值。我们可以绘制每日和每周的时间序列,以比较六个月期间的数据集。

  1. 让我们看看 2016 年的最后六个月。让我们从初始化变量开始:
start, end = '2016-01', '2016-06'
  1. 接下来,让我们使用这里给出的代码绘制图表:
fig, ax = plt.subplots()

ax.plot(df_power.loc[start:end, 'Solar'],
marker='.', linestyle='-', linewidth=0.5, label='Daily')
ax.plot(power_weekly_mean.loc[start:end, 'Solar'],
marker='o', markersize=8, linestyle='-', label='Weekly Mean Resample')
ax.set_ylabel('Solar Production in (GWh)')
ax.legend();

这里给出了前面代码的输出:

前面的截图显示,每周平均时间序列随着时间的推移而增加,并且比每日时间序列平滑得多。

摘要

在本章中,我们讨论了如何使用 pandas 库导入、清理、分析和可视化时间序列数据集。此外,我们使用matplotlibseaborn库可视化了时间序列数据集。最后,我们使用 Python 来加载和检查开放电力系统数据数据集,并执行了几项与 TSA 相关的技术。

在下一章中,我们将学习使用经典机器学习技术和三种不同类型的机器学习进行模型开发的不同方法,即监督学习、无监督机器学习和强化学习。

进一步阅读

  • P 实用时间序列分析,作者:阿维石·帕尔博士PKS·普拉卡什博士**帕克特出版
  • Python 机器学习-第三版,作者:塞巴斯蒂安·拉什卡瓦希德·米尔贾利利**帕克特出版
  • Python 数据分析大卫·泰伯帕克特出版
  • Python 回归分析,作者:卢卡·马萨龙阿尔贝托·博切蒂**帕克特出版
  • 机器学习统计,作者:普拉塔·丹盖提T5】帕克特出版
  • 数据科学统计,作者:詹姆斯·米勒帕克特出版
  • 一周内的数据科学算法-第二版,作者:达维德·纳廷加帕克特出版
  • sci kit 机器学习-学习快速入门指南,作者:凯文·乔利帕克特出版

九、假设检验和回归

在本章中,我们将深入探讨两个重要的概念,假设检验和回归。首先,我们将讨论假设检验的几个方面,假设检验的基本原则和假设检验的类型,并通过一些工作实例来运行。接下来,我们将讨论回归的类型,并使用 scikit-learn 库开发模型。

在本章中,我们将涵盖以下主题:

  • 假设检验
  • 黑客攻击
  • 理解回归
  • 回归类型
  • 模型开发和评估

技术要求

本章的代码可以在第九章假设检验和回归的文件夹内的 GitHub 存储库中找到。

假设检验

假设检验经常被用来促进使用实验数据集的统计决策。测试用于验证关于总体参数的假设。例如,考虑以下语句:

  • 在尼泊尔大学学习机器学习课程的学生平均成绩是 78 分。
  • 在学习机器学习课程的学生中,男生的平均身高高于女生。

在所有这些例子中,我们假设一些统计事实来证明这些说法。像这样的情况是假设检验有帮助的。假设检验评估关于任何特定人群的两个互斥陈述,并确定哪一个陈述最好由样本数据建立。这里,我们使用了两个基本关键词:人口和样本。总体包括一组数据中的所有元素,而样本由取自任何特定总体的一个或多个观察值组成。

在下一节中,我们将讨论假设测试,并讨论如何使用 Python 库来执行假设测试。

假设检验原理

假设检验基于统计学的两个基本原则,即标准化和标准标准化:

  • 规范化:规范化的概念因上下文而异。为了容易理解标准化的概念,它是在执行描述性统计之前,将在不同尺度上测量的值调整到公共尺度的过程,并由以下等式表示:

  • 标准归一化:标准归一化除了均值为 0,标准差为 1 之外,与归一化类似。标准归一化由以下等式表示:

除了这些概念,我们还需要了解假设检验的一些重要参数:

  • 零假设是基于领域知识做出的最基本的假设。比如一个人的平均打字速度是每分钟 38-40 个字。
  • 一个替代假设是反对无效假设的不同假设。这里的主要任务是我们是否接受或拒绝基于实验结果的替代假设。比如一个人的平均打字速度总是低于每分钟 38-40 个字。基于某些事实,我们可以接受或拒绝这个假设。例如,我们可以找到一个可以以每分钟 38 个单词的速度打字的人,这将推翻这个假设。因此,我们可以拒绝这种说法。
  • 第一类错误第二类错误:当我们接受或拒绝一个假设时,我们可能会犯两种错误。它们被称为第一类和第二类错误:
    • 假阳性:第一类错误是当 H0 为真时,我们拒绝零假设(H0)。
    • 假阴性:第二类错误是当 H0 为假时,我们不拒绝零假设(H0)。
  • P 值:这也称为概率值或渐近显著性。假设零假设为真,这是特定统计模型的概率。通常,如果 P 值低于预定阈值,我们拒绝零假设。
  • 显著性水平:这是你在使用假设之前应该熟悉的最重要的概念之一。重要程度是我们接受或拒绝无效假设的重要程度。我们必须注意,100%的准确性是不可能接受或拒绝的。我们通常根据我们的主题和领域来选择重要程度。一般是 0.05 或 5%。这意味着我们的输出应该有 95%的把握支持我们的零假设。

总而言之,在选择或拒绝零假设之前,请查看条件:

# Reject H0
p <= α
# Accept the null hypothesis
p > α

通常,在开始计算新值之前,我们会设置显著性级别。接下来,我们将看到如何使用统计库来执行假设检验。

statsmodels 库

让我们使用统计库进行假设检验。让我们考虑以下场景。

在一项关于青少年心理健康的研究中,48%的父母认为社交媒体是青少年压力的原因:

  • 人口:有青少年的父母(年龄> = 18 岁)
  • 感兴趣参数 : p
  • 无效假设 : p = 0.48
  • 替代假设 : p > 0.48

数据:调查了 4500 人,65%的被调查者认为自己青少年的压力来源于社交媒体。

让我们开始假设检验:

  1. 首先,导入所需的库:
import statsmodels.api as sm
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
  1. 接下来,让我们声明变量:
n = 4500
pnull= 0.48
phat = 0.65
  1. 现在,我们可以使用proportions_ztest方法来计算新的 P 值。查看以下代码片段:
sm.stats.proportions_ztest(phat * n, n, pnull, alternative='larger')

前面代码的输出如下:

(23.90916877786327, 1.2294951052777303e-126)

我们计算的1.2294951052777303e-126的 P 值相当小,我们可以拒绝零假设,即社交媒体是青少年压力的原因。

平均阅读时间

假设一个阅读比赛是和一些成年人一起进行的。数据如下所示:

[236, 239, 209, 246, 246, 245, 215, 212, 242, 241, 219, 242, 236, 211, 216, 214, 203, 223, 200, 238, 215, 227, 222, 204, 200, 208, 204, 230, 216, 204, 201, 202, 240, 209, 246, 224, 243, 247, 215,249, 239, 211, 227, 211, 247, 235, 200, 240, 213, 213, 209, 219,209, 222, 244, 226, 205, 230, 238, 218, 242, 238, 243, 248, 228,243, 211, 217, 200, 237, 234, 207, 217, 211, 224, 217, 205, 233, 222, 218, 202, 205, 216, 233, 220, 218, 249, 237, 223]

现在,我们的假设问题是这样的:随机学生(成人)的平均阅读速度是否超过每分钟 212 个单词?

我们可以将前面的概念分解为以下参数:

  • 人口:所有成年人
  • 感兴趣参数 : μ,教室人数
  • 无效假设 : μ = 212
  • 替代假设 : μ > 212
  • 置信水平 : α = 0.05

我们知道所有需要的参数。现在,我们可以使用带有alternate="larger"statsmodels包进行 Z 测试:

import numpy as np

sdata = np.random.randint(200, 250, 89)
sm.stats.ztest(sdata, value = 80, alternative = "larger")

前面代码的输出如下:

(91.63511530225408, 0.0)

由于计算的 P 值(0.0)低于标准置信水平(α = 0.05),我们可以拒绝零假设。也就是说成年人平均阅读速度为每分钟 212 个单词的说法被否决了。

假设检验的类型

假设检验有不同的类型。最常用的如下:

  • z 检验
  • t 检验
  • 方差分析检验
  • 卡方测验

每种类型的测试都超出了本书的范围。我们建议查看维基百科或进一步阅读部分的链接,以获得关于它们的详细信息。然而,我们将在本书中研究 Z 测试和 T 测试。在前面的例子中,我们只使用了 Z 检验。

t 检验

T 检验是推理统计学中最常用的一种检验。该测试最常用于我们需要了解两组平均值之间是否存在显著差异的场景。例如,假设我们有一个特定班级学生的数据集。数据集包含每个学生的身高。我们正在检查平均身高是否为 175 厘米:

  • 人口:该班所有学生
  • 感兴趣参数 : μ,教室人数
  • 零假设:平均身高μ = 175
  • 替代假设 : μ > 175
  • 置信水平 : α = 0.05

我们已经列出了所有的参数。现在,我们可以使用假设检验:

  1. 让我们首先设置数据集:
import numpy as np
height = np.array([172, 184, 174, 168, 174, 183, 173, 173, 184, 179, 171, 173, 181, 183, 172, 178, 170, 182, 181, 172, 175, 170, 168, 178, 170, 181, 180, 173, 183, 180, 177, 181, 171, 173, 171, 182, 180, 170, 172, 175, 178, 174, 184, 177, 181, 180, 178, 179, 175, 170, 182, 176, 183, 179, 177])
  1. 接下来,我们将使用 SciPy 库中的统计模块。注意,在前面的例子中,我们使用了 statsmodels API 库。我们还可以继续使用它,但是我们这里的目的是向您介绍 SciPy 库的新模块。让我们导入库:
from scipy.stats import ttest_1samp
import numpy as np
  1. 现在,让我们使用 NumPy 库来计算平均高度:
height_average = np.mean(height)
print("Average height is = {0:.3f}".format(height_average))

前面代码的输出如下:

Average height is = 175.618
  1. 现在,让我们用 T 检验来计算新的 P 值:
tset,pval = ttest_1samp(height, 175)

print("P-value = {}".format(pval))

if pval < 0.05:
 print("We are rejecting the null Hypothesis.")
else:
  print("We are accepting the null hypothesis.")

前面代码的输出如下:

Average height is = 175.618
P-value = 0.35408130524750125
We are accepting the null hypothesis

请注意,我们的显著性水平(α= 0.05)和计算的 P 值是 0.354。因为它大于α,所以我们接受零假设。这意味着学生的平均身高为 175 厘米,置信度为 95%。

黑客攻击

黑客攻击是一个严重的方法论问题。它也被称为数据钓鱼、数据屠杀或数据挖掘。滥用数据分析来检测数据中可能具有统计意义的模式。这是通过进行一项或多项测试并只发布那些具有更高意义的结果来完成的。

我们在前一节假设检验中已经看到,我们依靠 P 值得出结论。简而言之,这意味着我们计算 P 值,这是结果的概率。如果 P 值很小,则结果被宣布为具有统计学意义。这意味着,如果你创建一个假设并用一些标准来检验它,并报告一个小于 0.05 的 P 值,读者很可能会相信你已经找到了真正的相关性或效果。然而,这在现实生活中可能是完全错误的。根本不会有任何影响或关联。所以,无论报道什么都是一个fT4】也是一个阳性。这一点在出版物领域见得很多。许多期刊只会发表至少能报告一个统计学显著效应的研究。因此,研究人员试图争论数据集,或者实验得到一个明显更低的 P 值。这叫做黑客入侵。

已经介绍了数据挖掘的概念,现在是我们开始学习如何构建模型的时候了。我们将从最常见和最基本的模型之一——回归开始。

理解回归

我们用统计学术语中的相关性来表示两个定量变量之间的关联。请注意,我们使用了术语定量变量。这对你应该是有意义的。如果没有,我们建议您在此暂停,浏览第 1 章探索性数据分析基础

谈到数量变量和相关性,我们也假设关系是线性的,即一个变量增加或减少一个固定的量,而另一个变量增加或减少。要确定类似的关系,还有另一种在这些情况下经常使用的方法,回归,包括确定关系的最佳直线。一个简单的方程,称为回归方程,可以表示以下关系:

让我们来看看这个公式:

  • Y =因变量(你试图预测的变量)。它通常被称为结果变量
  • X =自变量(你用来预测 Y 的变量)。它通常被称为预测因子,或协变量特征
  • a =截距。
  • b =坡度。
  • u =回归残差。

如果y代表因变量,x代表自变量,这种关系被描述为yx的回归。xy之间的关系一般用一个方程来表示。等式显示了y相对于x的变化程度。

人们使用回归分析有几个原因。最明显的原因如下:

  • 我们可以使用回归分析来预测未来的经济状况、趋势或价值。
  • 我们可以使用回归分析来确定两个或多个变量之间的关系。
  • 我们可以使用回归分析来理解当一个变量改变时,另一个变量是如何改变的。

在后面的部分,我们将使用模型开发的回归函数来预测因变量,同时在函数中实现一个新的解释变量。基本上,我们会建立一个预测模型。所以,让我们深入研究回归。

回归类型

两种主要的回归类型是线性回归和多元线性回归。大多数简单的数据可以用线性回归来表示。一些复杂的数据遵循多元线性回归。在本章中,我们将研究 Python 的回归类型。最后,我们将以一个非线性例子的不同方面来结束讨论。

简单线性回归

线性回归又称简单线性回归,用一条直线定义两个变量之间的关系。在线性回归过程中,我们的目标是通过找到定义直线的斜率和截距来绘制最接近数据的直线。简单线性回归的方程一般如下:

X为单一特征,Y为目标,ab分别为截距和斜率。问题是,我们如何选择ab?答案是选择误差函数最小的线路,u。这个误差函数也被称为损失或成本函数,它是直线和数据点之间垂直距离差的平方(忽略正负抵消)之和。

这个计算叫做普通最小二乘 ( OLS )。请注意,解释回归的每个方面都超出了本书的范围,我们建议您探索进一步阅读部分,以拓宽您对该主题的知识。

多元线性回归

多元线性回归的情况下,两个或多个自变量或解释变量与目标或因变量呈线性关系。自然界中大多数可线性描述的现象都是通过多元线性回归得到的。例如,任何项目的价格都取决于采购的数量、一年中的时间以及库存中可用的项目数量。例如,一瓶葡萄酒的价格主要取决于你买了多少瓶。此外,在圣诞节等节日期间,价格会高一点。此外,如果库存中的瓶子数量有限,价格可能会更高。在这种情况下,葡萄酒的价格取决于三个变量:数量、一年中的时间和库存数量。这种类型的关系可以使用多元线性回归来捕捉。

多元线性回归方程一般如下:

这里 Y 为因变量, X i s 为自变量。

非线性回归

非线性回归是一种回归分析,其中数据遵循模型,然后表示为数学函数。简单线性回归涉及两个变量( XY )与直线函数, ,而非线性回归必须生成一条曲线。非线性回归使用回归方程,如下所示:

让我们看看这个公式:

  • X= p 个预测因子的向量
  • β= k 个参数的向量
  • f(-) =一个已知的回归函数
  • ε =误差项

非线性回归可以拟合各种各样的曲线。它使用对数函数、三角函数、指数函数和许多其他拟合方法。这种建模类似于线性回归建模,因为两者都试图从一组变量中图形化地控制特定的答案。开发这些模型比开发线性模型更复杂,因为函数是通过一系列近似(迭代)生成的,这些近似可能是反复试验的结果。数学家使用各种各样的既定方法,如高斯-牛顿法和莱文伯格-马夸特法。这种非线性模型生成曲线的目的是使 OLS 尽可能小。OLS 越小,函数越适合数据集的点。它测量有多少观测值不同于数据集平均值。

在下一节中,我们将学习如何使用 Python 库开发和评估回归模型。

模型开发和评估

在前一节中,我们从理论上讨论了不同类型的回归。既然我们已经涵盖了理论概念,是时候获得一些实践经验了。在本节中,我们将使用 scikit-learn 库来实现线性回归并评估模型。为此,我们将使用著名的波士顿住房定价数据集,该数据集被研究人员广泛使用。我们将讨论回归情况下使用的不同模型评估技术。

让我们尝试在前面看到的解释的基础上开发一些回归模型。

构建线性回归模型

任何数据科学专业人员在解决任何回归问题时想到的第一个概念是构建线性回归模型。线性回归是最古老的算法之一,但它仍然非常有效。我们将使用示例数据集在 Python 中构建一个线性回归模型。该数据集在 scikit-learn 中作为一个名为波士顿房价数据集的样本数据集提供。我们将使用sklearn库加载数据集并构建实际模型。让我们从加载和理解数据开始:

  1. 让我们从导入所有必要的库并创建我们的数据框开始:
# Importing the necessary libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns 
from sklearn.datasets import load_boston

sns.set(style="ticks", color_codes=True)
plt.rcParams['figure.figsize'] = (8,5)
plt.rcParams['figure.dpi'] = 150

# loading the data
df = pd.read_csv("https://raw.githubusercontent.com/PacktPublishing/hands-on-exploratory-data-analysis-with-python/master/Chapter%209/Boston.csv")
  1. 现在,我们已经将数据集加载到boston变量中。我们可以按如下方式查看数据框的键:
print(df.keys())

这会将所有键和值作为 Python 字典返回。前面代码的输出如下:

Index(['CRIM', ' ZN ', 'INDUS ', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'LSTAT', 'MEDV'], dtype='object')
  1. 现在我们的数据已经加载,让我们快速准备好我们的数据帧并继续工作:
df.head()
# print the columns present in the dataset
print(df.columns)
# print the top 5 rows in the dataset
print(df.head()) 

前面代码的输出如下:

Figure 9.1: The first five rows of the DataFrame

MEDV是目标变量,在建立模型时,它将被用作目标变量。目标变量( y )与特征变量( x )是分开的。

  1. 在新的整体数据框架中,让我们检查是否有任何缺失的值:
df.isna().sum()

看看下面的输出:

CRIM 0
ZN 0
INDUS 0
CHAS 0
NOX 0
RM 0
AGE 0
DIS 0
RAD 0
TAX 0
PTRATIO 0
LSTAT 0
MEDV 0
dtype: int64

特别是在回归的情况下,确保我们的数据没有任何缺失值是很重要的,因为如果数据有缺失值,回归就不会起作用。

相关性分析是建立任何模型的关键部分。我们必须了解数据的分布以及自变量与因变量之间的关系。

  1. 让我们绘制一个热图,描述数据集中各列之间的相关性:
#plotting heatmap for overall data set
sns.heatmap(df.corr(), square=True, cmap='RdYlGn')

输出图如下所示:

Figure 9.2: Correlation matrix generated from the preceding code snippet

既然要建立线性回归模型,那就找几个与MEDV有显著相关性的自变量。从前面的热图来看,RM(每套住房的平均房间数)与MEDV(1000 美元中自有住房的中位数)正相关,因此我们将把RM作为特征( X )和MEDV作为线性回归模型的预测因子( y )。

  1. 我们可以用lmplot的方法从海鸟身上看到RMMEDV之间的关系。查看以下代码片段:
sns.lmplot(x = 'RM', y = 'MEDV', data = df)

前面代码的输出如下:

Figure 9.3: Lmplot illustrating the relationship between the RM and MEDV columns

前面的截图显示了这两个变量之间的强相关性。然而,我们可以很容易地从图表中发现一些异常值。接下来,让我们为模型开发创造条件。

Scikit-learn 需要在数组中创建特征和目标变量,所以在给Xy分配列时要小心:

# Preparing the data
X = df[['RM']]
y = df[['MEDV']]

现在我们需要将数据分成训练集和测试集。Sklearn 提供了一些方法,通过这些方法,我们可以将原始数据集分割成训练数据集和测试数据集。我们已经知道,回归模型开发背后的原因是为了得到一个预测未来产量的公式。但是我们如何确定模型预测的准确性呢?衡量模型准确性的一种逻辑技术是将正确预测的数量除以测试的观察总数。

对于这个任务,我们必须有一个已经知道输出预测的新数据集。在模型开发过程中,最常用的技术是数据集的训练/测试分割。这里,您将数据集分为训练数据集和测试数据集。我们将模型训练或拟合到训练数据集,然后通过对测试(标记或预测)数据集进行预测来计算精度。

  1. 这是使用sklearn.model_selection中的train_test_split()功能完成的:
# Splitting the dataset into train and test sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 10)

X是我们这里的自变量,Y是我们的目标(输出)变量。在train_test_split中,test_size表示测试数据集的大小。test_size是用于测试数据集的数据比例。在这里,我们为test_size传递了一个0.3的值,这意味着我们的数据现在分为 70%的训练数据和 30%的测试数据。最后,random_state为随机数生成器设置种子,随机数生成器分割数据。train_test_split()函数将返回四个数组:训练数据、测试数据、训练输出和测试输出。

  1. 现在最后一步是训练线性回归模型。从极其强大的sklearn库中,我们导入LinearRegression()函数来将我们的训练数据集拟合到模型中。当我们运行LinearRegression().fit()时,该函数会自动计算我们前面讨论过的 OLS,并生成一个适当的直线函数:
#Training a Linear Regression Model
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()

# Fitting the training data to our model
regressor.fit(X_train, y_train)

现在,我们有了一个名为regressor的模型,它是在训练数据集上完全训练的。下一步是评估模型对目标变量的正确预测程度。

模型评估

我们的线性回归模型现在已经被成功训练。请记住,我们从数据集中分离了一些数据用于测试,我们打算使用这些数据来发现模型的准确性。我们将用它来评估我们模型的效率。r2-统计学是测量回归模型精度的常用方法:

  1. R 2 可以使用我们在LinearRegression.score()方法中的测试数据集来确定:
#check prediction score/accuracy
regressor.score(X_test, y_test)

score()功能的输出如下:

0.5383003344910231

score(y_test, y_pred)方法预测输入集XY值,并将它们与真实的Y值进行比较。R 2 的值一般在 0 到 1 之间。R 2 的值越接近 1,模型越精确。这里 R 2 评分是 0.53 ≈ 53%准确率,还可以。有了一个以上的自变量,我们将提高模型的性能,这将是我们接下来要研究的。

  1. 在此之前,让我们用我们的模型来预测 y 值,并对其进行更多的评估。并且还建立了目标变量DataFrame:
# predict the y values
y_pred=regressor.predict(X_test)
# a data frame with actual and predicted values of y
evaluate = pd.DataFrame({'Actual': y_test.values.flatten(), 'Predicted': y_pred.flatten()})
evaluate.head(10)

目标变量DataFrame如下:

Figure 9.4: The first 10 entries showing the actual values and the predicted values

前面的截图显示了实际值和预测值之间的差异。如果我们把它们画出来,我们就能看到它们:

evaluate.head(10).plot(kind = 'bar')

前面代码的输出如下:

Figure 9.5: Stacked bar plot showing the actual values and the predicted values

更容易理解,对吧?请注意,大多数预测值低于实际值。

计算精度

Sklearn 提供的指标可以帮助我们用多种公式评估我们的模型。用于评估模型的三个主要指标是平均绝对误差、均方误差和 R 2 分数。

让我们快速尝试这些方法:

# Scoring the model
from sklearn.metrics import r2_score, mean_squared_error,mean_absolute_error

# R2 Score
print(f"R2 score: {r2_score(y_test, y_pred)}")

# Mean Absolute Error (MAE)
print(f"MSE score: {mean_absolute_error(y_test, y_pred)}")

# Mean Squared Error (MSE)
print(f"MSE score: {mean_squared_error(y_test, y_pred)}")

前面代码的输出如下:

R2 score: 0.5383003344910231
MSE score: 4.750294229575126
MSE score: 45.0773394247183

请注意,我们不是在评估我们在前面的输出中获得的精度。在任何机器学习场景中,我们都试图通过执行几种优化技术来提高准确性。

理解准确性

我们使用 scikit-learn 库来训练回归模型。除此之外,我们还使用训练好的模型来预测一些数据,然后计算精度。例如查看图 9.5 。第一个条目说实际值是 28.4,但是我们训练的回归模型预测它是 25.153909。因此,我们有一个 28.4 - 25.153909 = 3.246091 的差异。让我们试着理解这些差异是如何理解的。让 x i 为实际值,为任意样本i的模型预测值。

误差由以下公式给出:

对于任何样本i,我们都可以得到预测值和实际值的差值。我们可以通过对误差求和来计算平均误差,但是由于有些误差是负的,有些是正的,所以它们很可能会相互抵消。那么,问题仍然存在,我们如何知道我们的训练模型在所有数据集上的表现有多准确?这就是我们使用平方误差概念的地方。你应该知道正数和负数的平方总是正数。因此,他们没有机会互相抵消。因此,我们可以用下面的等式来表示平方误差:

一旦我们知道如何计算平方误差,我们就可以计算均方差。那很容易,对吗?当然,为了计算均方误差,我们可以使用以下公式:

现在,如果我们取均方误差的根,我们得到另一个精度度量,称为均方误差的根 ( RMSE )。等式现在变成了:

另一种广泛使用的精度测量方法叫做相对均方误差 ( 均方根误差)。不要把它和 RMSE 混淆了。计算均方根误差的公式如下:

在上式中, E(x) 被称为 x 的期望值,除了 rMSE,我们还使用了 R 2 方法。R 2 的计算公式如下:

在数据科学中经常看到的另一种精度度量是绝对误差。顾名思义,它取绝对值并计算总和。测量绝对误差的公式如下:

最后,除了绝对误差之外,还可以使用另一种类型的误差,即平均绝对误差。平均绝对误差的计算公式如下:

太多了吗?曾经是。然而,如果你仔细检查这些方程,你会发现它们是非常密切相关的。试着把重点放在名字上,这解释了准确度测量的作用。现在,每当你看到任何数据科学模型使用这些精度测量,它会更有意义,不是吗?

祝贺您了解准确度测量。在下一节中,让我们深入研究多元线性回归,我们将尝试使用这些精度度量。

实现多元线性回归模型

当因变量依赖于几个自变量时,可以使用多元线性回归来获取关系。多元线性回归可以看作是简单线性回归的延伸。说到使用 sklearn 实现多元线性回归,简单和多元线性回归没有太大区别:

  1. 只需在X变量中包含额外的列并运行代码。因此,让我们包含X变量的附加列,并遵循相同的代码。
  2. 记住,二维线性回归模型是一条直线;它是三维的平面,也是三维以上的超平面:
# Preparing the data
X = df[['LSTAT','CRIM','NOX','TAX','PTRATIO','CHAS','DIS']]
y = df[['MEDV']]

# Splitting the dataset into train and test sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 10)

# Fitting the training data to our model
regressor.fit(X_train, y_train)

#score of this model
regressor.score(X_test, y_test)

score()功能的输出如下:

0.6446942534265363
  1. 让我们用我们的模型预测y值并评估它:
# predict the y values
y_pred=regressor.predict(X_test)
# a data frame with actual and predicted values of y
evaluate = pd.DataFrame({'Actual': y_test.values.flatten(), 'Predicted': y_pred.flatten()})
evaluate.head(10)

目标变量DataFrame如下:

Figure 9.6: The first 10 entries showing the actual values and the predicted values

  1. 让我们制作另一个特征较少的多元线性回归模型:
# Preparing the data
X = df[['LSTAT','CRIM','NOX','TAX','PTRATIO']]
y = df[['MEDV']]

# Splitting the dataset into train and test sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 10)

# Fitting the training data to our model
regressor.fit(X_train, y_train)

#score of this model
regressor.score(X_test, y_test)

score()功能的输出如下:

0.5798770784084717

该模型的准确率为 57%。下表显示了此构建模型的目标变量 MEDV 的实际值和预测值,如下所示:

如你所见,在X中改变特征会使模型的精度发生变化。因此,您必须仔细分析特征之间的相关性,然后使用它们以更高的精度构建模型。

摘要

在本章中,我们讨论了两个重要的概念:假设检验和回归分析。在假设检验中,我们了解了假设、其基本原理以及不同类型的假设检验,并且我们使用了两个不同的 Python 库(statsmodels 和 SciPy)来创建不同的假设检验。此外,我们还讨论了 p-hacking,这是假设测试期间最常遇到的挑战之一。接下来,我们讨论了不同类型的回归,并使用 scikit-learn 来构建、测试和评估一些回归模型。

在下一章中,我们将更详细地讨论模型开发和评估。除了回归之外,我们还将讨论其他几种可以使用的模型。

进一步阅读

  • Python 的回归分析,作者:卢卡·马萨隆阿尔贝托·博切蒂帕克特出版,2016 年 2 月 29 日
  • 机器学习统计普拉塔·丹盖提帕克特出版,2017 年 7 月 20 日
  • 数据科学统计詹姆斯·d·米勒帕克特出版,2017 年 11 月 17 日
  • 一周内的数据科学算法-第二版,作者:达维德·纳廷加帕克特出版,2018 年 10 月 31 日
  • sci kit 机器学习-学习快速入门指南,作者:凯文·乔利帕克特出版,2018 年 10 月 30 日

十、模型开发和评估

到目前为止,我们已经讨论了几种探索性数据分析 ( EDA )技术。我们执行 EDA 的原因是准备数据集并理解它,以便它可以用于预测和分析目的。我们所说的预测性和分析性是指创建和评估机器学习 ( ML )模型。在本章中,我们将为数据科学奠定基础,了解可以构建的不同类型的模型,以及如何对它们进行评估。

在本章中,我们将涵盖以下主题:

  • 机器学习的类型
  • 理解监督学习
  • 理解无监督学习
  • 理解强化学习
  • 统一机器学习工作流

技术要求

在本章中,我们将使用一些聚类算法。我们将在本章中使用的数据集可以在https://github . com/sureshHARDIYA/PhD-resources/blob/master/Data/Review % 20 paper/ACM/preferred . xlsx?原始=真

我们还将使用 scikit-learn 库中的一些模块,包括MiniBatchKMeansTfidfVectorizerPCATSNE

机器学习的类型

机器学习 ( ML )是计算机科学的一个领域,它处理的是无需明确编程就能自己发现模式的算法的创建。ML 算法有不同的类型,这些算法分为三个不同的类别,如下图所示:

如上图所示,ML 算法有三种不同的类别:

  • 监督学习
  • 无监督学习
  • 强化学习

我们将在接下来的章节中简要讨论这些算法。

理解监督学习

监督学习的主要目标是从标记的训练数据中归纳出一个模型。一旦一个模型被训练好,它就允许用户对看不见的未来数据做出预测。这里,我们所说的标注训练数据是指训练示例知道相关的输出标签。因此,它被称为监督学习。学习过程可以被认为是老师监督整个过程。在这样的学习过程中,我们最初知道正确的答案,学生们随着时间的推移迭代学习足够多,并试图回答看不见的问题。答案中的错误被老师纠正了。当我们能够确保学生的表现达到可接受的水平时,学习的过程就停止了。

*在监督学习中,我们有输入变量(x i )和输出变量(Y i )。有了这个,我们可以学习一个函数,f,如下式所示:

目标是学习一个通用的映射函数f,以便该函数可以为任何新的输入数据x预测输出变量Y。监督学习算法可以分为两组,如下所示:

  • 回归
  • 分类

让我们简单看一下这些。

回归

回归问题有一个输出变量或因变量。这是一个真实值,例如体重、年龄或任何其他真实数字。我们在第 9 章假设检验和 R 回归T7】中详细讨论了回归,包括不同类型的回归(简单线性回归、多元线性回归和非线性回归),并使用波士顿住房数据集进行回归分析。

既然我们在第 9 章假设检验和 R 回归中讨论了回归问题,我们就继续学习分类问题。

分类

分类问题具有类别值形式的输出变量;例如白葡萄酒;年轻人、成年人或老年人。对于分类问题,有不同类型的分类算法。

一些最受欢迎的如下:

  • 线性分类器:朴素贝叶斯分类器,逻辑回归,线性 SVM

  • 最近邻

  • 决策树分类器

  • 支持向量机

  • 随机森林分类器

  • 神经网络分类器

  • 增强树分类器

列出了最流行的分类算法后,我们必须指出,浏览每一种分类算法都超出了本书的范围。然而,我们在这里的主要意图是给你指出正确的方向。我们建议您查看本书的进一步阅读部分,了解关于各自主题的更多细节。

关于如何在 Python 中用红葡萄酒和白葡萄酒数据集实现这些分类器的概念证明可以在第 11 章关于葡萄酒质量数据分析的 EDA中找到。我们也将在那一章讨论不同的评估技术,这些技术可以用于分类目的。

理解无监督学习

无监督机器学习处理未标记的数据。这种类型的学习可以发现数据中的各种未知模式,并可以促进有用的分类。考虑一个场景,患者使用在线网络应用程序来了解疾病,了解他们的症状,并管理他们的疾病。这种提供关于某些疾病的心理教育的网络应用程序被称为“互联网提供的治疗”(【IDT】)。想象一下,几千名患者在一天中不同的时间访问网站,了解他们的病情,他们的所有活动都被记录到我们的数据库中。当我们分析这些日志文件并使用散点图绘制它们时,我们发现一大群患者在下午访问网站,一大块在晚上访问网站。其他一些患者也遵循随机登录模式。这个场景说明了两个不同的病人群:一个在下午活跃,一个在晚上活跃。这个典型的场景是一个集群任务的例子。

我们可以使用几种类型的无监督学习算法。然而,两个主要的无监督学习任务是聚类降维。在下一节中,我们将更多地讨论无监督学习算法的不同应用。

无监督学习的应用

无监督学习算法有几种应用。下面我们来看几个:

  • 聚类:这些类型的算法允许我们将数据集分类成几个相似的组,称为一个聚类。每个簇代表一组相似的点。
  • 关联挖掘:这些类型的无监督学习算法允许我们在数据集中找到频繁出现的项目。
  • 异常检测:这些类型的无监督学习算法帮助我们确定任何现有数据集中的异常数据点。
  • 降维:这些技术常用于数据处理,目的是减少数据集中的特征数量。这是无监督学习中最重要的任务之一。

基于小批量 K 均值聚类的聚类

在本节中,我们将使用无监督学习算法之一,即聚类。具体来说,我们将基于一个名为 MiniBatch K-means 聚类算法的算法对文本进行聚类。让我们了解一下这方面的背景。

每当研究人员开始在任何特定领域工作时,他们都会进行各种文献综述,以了解任何特定领域的技术水平。这样的研究被称为综述论文。撰写此类综述论文时,您需要设置一组搜索关键词,并在许多研究论文索引数据库中执行搜索,例如 scholar.google.com(https://scholar.google.com/)。在几个数据库中执行搜索后,您将有一个想要研究的相关文章的列表。在这种情况下,我们已经执行了搜索,相关文章的列表已经以 Excel 表格的形式提供。请注意,Excel 文件中的每一行都包含一些关于相关论文的元数据。

You can find out more about the MiniBatch K-means clustering algorithm by looking at the official documentation of the sklearn library: https://scikit-learn.org/stable/modules/generated/sklearn.cluster.MiniBatchKMeans.html.

了解了上下文之后,让我们将数据集加载到笔记本中。这对我们来说应该不是什么谜了:

  1. 让我们加载 Excel 文件:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import seaborn as sns

sns.set()
plt.rcParams['figure.figsize'] = (14, 7)

df = pd.read_excel("https://github.com/sureshHARDIYA/phd-resources/blob/master/Data/Review%20Paper/acm/preprocessed.xlsx?raw=true")
  1. 接下来,让我们检查前 10 个条目,以了解数据是什么样子的:
df.head(10)

前面代码的输出如下:

如我们所见,有几列。我们只对研究论文的标题感兴趣。因此,我们将只关注标题列。

提取关键词

下一步是从标题中提取关键词。我们有几种方法可以提取关键词。这里,我们将使用sklearn.feature_extraction模块提供的TfidfVectorizer实用方法。让我们开始吧:

  1. 要使用该库,我们需要导入基本库:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import MiniBatchKMeans
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
  1. 接下来,让我们学习如何提取关键词:
tfidf = TfidfVectorizer(
   min_df = 5,
    max_df = 0.95,
    max_features = 8000,
    stop_words = 'english'
)
tfidf.fit(df.Title)
text = tfidf.transform(df.Title)

You can find out more about TfidfVectorizer by reading the official documentation: https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html.

在前面的代码中,我们将标题转换为 TF-IDF 特性。我们正在从标题中删除停止词。

You can read more about stop words at https://nlp.stanford.edu/IR-book/html/htmledition/dropping-common-terms-stop-words-1.html.

如果你理解了集群的概念,你可能已经理解了围绕集群的最大挑战之一;也就是说,确定有多少集群是最佳的。有一些算法可以帮助我们确定最佳的聚类数。其中一个算法是肘关节法(https://www.scikit-yb.org/en/latest/api/cluster/elbow.html)。

让我们创建一个函数,获取文本和最大数量的聚类,并将它们绘制在图表上。这样做的代码如下:

def generate_optimal_clusters(data, max_k):
    iters = range(2, max_k+1, 2)

    sse = []
    for k in iters:
        sse.append(MiniBatchKMeans(n_clusters=k, init_size=1024, batch_size=2048, random_state=20).fit(data).inertia_)
        print('Fitting {} clusters'.format(k))

    f, ax = plt.subplots(1, 1)
    ax.plot(iters, sse, marker='o')
    ax.set_xlabel('Cluster Centers')
    ax.set_xticks(iters)
    ax.set_xticklabels(iters)
    ax.set_ylabel('SSE')
    ax.set_title('SSE by Cluster Center Plot')

generate_optimal_clusters(text, 20)

关于前面的功能,请注意以下几点:

  • 它需要两个参数,文本和最大聚类数。在这种情况下,我们假设集群的最大数量是 20。
  • 接下来,在函数内部,我们调用MiniBatchKMeans簇上的fit()方法,范围从 2 到允许的最大簇数(2 到 20)。
  • 对于每个聚类,我们计算图中的平方误差之和 ( SSE )。

前面代码的输出如下:

如上图所示,弯头在 4 处发生变化。根据弯头理论,该图在最佳簇号处创建弯头。因此,在这种情况下,最佳集群是 4。

绘制集群

现在,让我们在图上画出这些簇。我们将开始使用主成分分析 ( 主成分分析)进行绘图,因为它擅长捕捉数据的全局结构。然后,我们将使用t-分布式随机邻居嵌入 ( TSNE )来绘制图表,因为它擅长捕捉与邻居的关系。让我们开始吧:

  1. 让我们从再次创建模型开始:
clusters = MiniBatchKMeans(n_clusters=4, init_size=1024, batch_size=2048, random_state=20).fit_predict(text)
  1. 让我们绘制两张图表。首先,我们将使用主成分分析技术绘图,然后使用 TSNE 技术。使用以下代码来完成此操作:
max_label = max(clusters)
max_items = np.random.choice(range(text.shape[0]), size=3000, replace=True)
pca = PCA(n_components=2).fit_transform(text[max_items,:].todense())
tsne = TSNE().fit_transform(PCA(n_components=50).fit_transform(text[max_items,:].todense()))

idx = np.random.choice(range(pca.shape[0]), size=300, replace=True)
label_subset = clusters[max_items]
label_subset = [cm.hsv(i/max_label) for i in label_subset[idx]]

f, ax = plt.subplots(1, 2, figsize=(14, 6))
ax[0].scatter(pca[idx, 0], pca[idx, 1], c=label_subset)
ax[0].set_title('Generated PCA Cluster Plot')

ax[1].scatter(tsne[idx, 0], tsne[idx, 1], c=label_subset)
ax[1].set_title('Generated TSNE Cluster Plot')

前面代码的输出如下:

每种颜色代表一种集群。在前面的代码中,我们对特征进行了采样,只捕获了 3,000 个文档以加快处理速度,并使用散点图绘制了它们。对于主成分分析,我们将维度减少到 50。

You can learn more about TSNE from the official website: https://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html.

请注意,很难找出在每种类型的集群中找到了哪些关键字。为了更好地可视化这一点,我们需要绘制每个集群的单词 cloud。

单词云

为了看到属于每个聚类的前几个关键词,我们需要创建一个函数,为我们提供每个聚类的前 50 个单词,并绘制单词云。

检查功能,如下所示:

from wordcloud import WordCloud 

fig, ax = plt.subplots(4, sharex=True, figsize=(15,10*4))

plt.rcParams["axes.grid"] = False

def high_frequency_keywords(data, clusters, labels, n_terms):
    df = pd.DataFrame(data.todense()).groupby(clusters).mean()

    for i,r in df.iterrows():
      words = ','.join([labels[t] for t in np.argsort(r)[-n_terms:]])
      print('Cluster {} \n'.format(i))
      print(words)
      wordcloud = WordCloud(max_font_size=40, collocations=False, colormap = 'Reds', background_color = 'white').generate(words)
      ax[i].imshow(wordcloud, interpolation='bilinear')
      ax[i].set_title('Cluster {} '.format(i), fontsize = 20) 
      ax[i].axis('off') 
high_frequency_keywords(text, clusters, tfidf.get_feature_names(), 50)

前面代码的输出被分成两部分。让我们看看文本输出:

Cluster 0 
bipolar,patient,framework,evaluation,risk,older,internet,healthcare,activity,approach,online,anxiety,research,digital,children,assessment,clinical,dementia,adaptive,cognitive,intervention,disorders,technology,learning,psychiatric,community,interventions,management,therapy,review,adults,use,support,designing,schizophrenia,stress,data,people,analysis,care,self,mobile,disorder,using,patients,design,study,treatment,based,depression

Cluster 1 
cessation,brief,comparing,single,disorder,people,adults,symptoms,risk,clinical,women,prevention,reduce,improve,training,use,results,online,personalized,internet,cluster,alcohol,anxiety,feedback,efficacy,patients,health,mental,therapy,primary,help,self,program,care,effects,cognitive,pilot,treatment,depression,tailored,effectiveness,web,based,randomised,study,intervention,protocol,randomized,controlled,trial

Cluster 2 
qualitative,physical,digital,implementation,self,medical,management,patient,adults,designing,life,quality,work,development,systems,data,related,children,persons,support,online,analysis,assessment,information,intervention,veterans,service,design,patients,problems,behavioral,using,research,systematic,disorders,use,interventions,primary,treatment,based,study,services,review,severe,people,community,illness,care,mental,health

Cluster 3 
modeling,implications,ethical,emotion,behavioral,dementia,based,young,designing,homeless,dynamics,group,experiences,robot,predicting,mobile,game,depression,understanding,physical,people,challenges,therapy,study,patients,management,technology,impact,technologies,self,anxiety,use,skills,interaction,networking,personal,disclosure,sites,data,networks,disclosures,using,design,online,network,support,mental,health,media,social

请注意,它打印了四个不同的簇,每个簇中有 50 个经常出现的单词。很容易看到属于每个聚类的关键词,并判断聚类是否正确。为了正确呈现这些单词,我们生成了一个单词云。

字云如下:

我们可以看到,有四个集群。每个聚类显示最相关的单词。例如,聚类 0 显示了许多与医疗保健、干预、框架、数字健康等相关的单词。这样做,更容易看出关键词之间的关系。

在下一节中,我们将讨论强化学习。

理解强化学习

在强化学习中,代理改变其状态以最大化其目标。这里有四个截然不同的概念:代理人、国家、行动和奖励。让我们更详细地看看这些:

  • 特工:这是我们训练的节目。它从环境中的动作空间中为指定的任务选择动作。
  • 状态:这是代理从其环境接收到的观察,代表代理的当前情况。
  • 动作:这是代理从其动作空间做出的选择。该操作会更改代理的状态。
  • 奖励:这是关于代理人行为的结果反馈,描述了代理人应该如何表现。

下图说明了这些概念中的每一个:

如上图所示,强化学习涉及一个主体、一个环境、一组动作、一组状态和一个奖励系统。代理与环境交互并修改其状态。基于这种修改,它的输入得到奖励或惩罚。代理的目标是随着时间的推移使回报最大化。

监督学习和强化学习的区别

当我们有标记的训练数据集时,使用监督学习算法。强化学习用于代理与环境交互以观察基本行为并改变其状态以最大化其回报或目标的场景。

下表给出了它们之间的其他差异:

| 标准 | 监督学习 | 强化学习 |
| 例子 | 数字识别。 | 象棋比赛。 |
| 对…有效 | 给定标记数据集。 | 与给定的环境互动。 |
| 决定 | 根据开始时给出的输入做出决定。 | 在这里,算法帮助线性地做出决定。 |

RL 的一个基本特征是,代理的操作可能不会影响它正在工作的环境的当前状态,但会影响后续状态。因此,算法可能不会在初始状态下学习,但可以在一些状态改变后学习。

强化学习的应用

RL 算法有几个用例。

一些最重要的用例如下:

  • 文本挖掘:一些研究人员和公司已经开始使用基于 RL 的文本生成模型,从长文本中生成可读性很强的文本摘要。
  • Robotics :在机器人工程领域使用了多种基于深度 RL 的算法,来提升基于奖励系统的机器人性能。
  • 医疗保健:多项研究表明,RL 可用于医疗保健,优化用药剂量和治疗政策。
  • 交易:交易业务中使用了几种基于 RL 的模型来优化交易结果。

统一机器学习工作流

选择使用什么机器学习算法总是取决于您拥有的数据类型。如果你有一个带标签的数据集,那么你最明显的选择就是选择一种有监督的机器学习技术。此外,如果您的标记数据集包含目标变量的真实值,那么您将选择回归算法。最后,如果您的标记数据集在目标变量中包含一个分类变量,那么您将选择分类算法。无论如何,您选择的算法始终取决于您拥有的数据集类型。

类似地,如果数据集不包含任何目标变量,那么显而易见的选择是无监督算法。在本节中,我们将研究机器学习的统一方法。

机器学习工作流程可以分为几个阶段:

  • 数据预处理
  • 数据准备
  • 训练集和语料库创建
  • 模型创建和培训
  • 模型评估
  • 最佳模型选择和评估
  • 模型部署

机器学习算法的整个工作流程如下图所示:

如上图所示,任何机器学习工作流的第一步都是数据预处理。我们将在以下几节中简要解释每个阶段。

数据预处理

数据预处理涉及几个步骤,包括数据收集、数据分析、数据清理、数据规范化和数据转换。数据预处理的第一步是数据收集。让我们来看看。

数据收集

数据科学中,最重要的是数据。这些数据掌握着我们周围发生的任何事件、现象或实验的基本事实。一旦我们处理了数据,我们就会得到信息。一旦我们处理了这些信息,我们就可以从中获得知识。因此,知识提取中最突出的阶段是被捕获的数据的相关性。有不同类型的数据,包括结构化数据非结构化数据半结构化数据。结构化数据在所有观察中保持统一的结构,类似于关系数据库表。非结构化数据不维护任何特定的结构。半结构化数据在观察中保持一定的结构。 JavaScript 对象标注 ( JSON )是存储半结构化数据最流行的方式之一。

任何公司收集数据的过程都取决于需要研究的项目种类和信息类型。不同类型的数据集包括文本数据、文件、数据库、传感器数据和许多其他物联网 ( 物联网)数据。然而,当学习机器学习工作流程时,大多数学生更喜欢避开数据收集阶段,使用来自卡格尔和 UCI 机器学习资源库等地方的开源数据。

数据分析

这是我们执行探索性数据分析以了解数据集的初步分析阶段之一。我们在第 3 章EDA 和个人电子邮件分析中讨论了可以执行的几种技术。这一步告诉我们手头的数据类型、目标变量、数据中有多少行和列、每列的数据类型、缺少多少行、数据分布是什么样子等等。

数据清理、标准化和转换

我们在第 4 章数据转换中详细讨论了数据清理、规范化和数据转换。我们讨论了如何重新缩放数据集,如何将数据集转换为标准数据集,如何对数据进行二值化,以及如何执行一次性编码和标签编码。

经过这三个步骤后,我们丢失的数据将得到处理,噪声数据将被过滤,不一致的数据将被删除。

数据准备

有时,我们拥有的数据集并不总是处于适合机器学习算法使用的状态。在这种情况下,数据准备是我们能做的最基本的事情之一。我们需要集成来自多个来源的数据,执行切片和分组,并将它们聚合成正确的格式和结构。这一步被称为数据准备。

我们在第 6 章分组数据集中详细讨论了这个过程。需要注意的是,有些书认为数据预处理和数据准备是同一个步骤,因为有几个重叠的操作。

训练集和语料库创建

在数据准备步骤之后,得到的数据集被用作训练语料库。通常,训练语料库被分成三大块:训练集、验证集和测试集。

训练集是您用来训练一个或多个机器学习算法的数据块。验证集是用于验证训练模型的数据块。最后,测试集是你用来评估一个完全训练好的分类器性能的数据块。

模型创建和培训

一旦我们将数据集分成三大块,我们就可以开始训练过程了。我们使用训练集来构建机器学习模型。然后,我们使用验证集来验证模型。一旦模型已经被训练,我们使用测试集来找到模型的最终性能。

模型评估

基于测试数据的表现,我们可以创建一个混淆矩阵。该矩阵包含四个不同的参数:真阳性、真阴性、假阳性和假阴性。考虑以下混淆矩阵:

| | 预测:阳性 | 预测:阴性 |
| 实际:正 | 真阳性 | 假阴性 |
| 实际:负 | 假阳性 | 真负值 |

该矩阵显示了四个不同的参数:

  • 真阳性:当实际值为真时,模型预测为真。
  • 真否定:当实际值为假时,模型预测为假。
  • 误报:当实际值为假时,模型预测为真。这也被称为第一类错误。
  • 假阴性:当实际值为真时,模型预测为假。这也被称为第二类错误。

一旦我们知道了混淆矩阵,我们就可以计算模型的几个精度,包括精度、负谓词值、灵敏度、特异性和准确性。让我们一个接一个地看看它们,并了解它们是如何计算的。

精度是真阳性与真阳性和假阳性之和的比值。公式如下:

阴性预测值 ( 净现值)的公式如下:

相似度,灵敏度的公式如下:

特异性公式如下:

最后,模型的精度由以下公式给出:

我们来看一个例子。假设我们构建了一个监督分类算法,该算法查看窗口的图片,并将其分类为脏或不脏。最终的混淆矩阵如下:

| | 预测:肮脏 | 预测:不脏 |
| 实际:脏 | TP = 90 | FN = 40 |
| 实际:不脏 | FP = 10 | TN = 60 |

现在,让我们计算一下这种情况下的精度度量:

  • 精度= TP / (TP + FP) = 90 /(90 + 10) = 90%。这意味着 90%被归类为脏的图片实际上是脏的。
  • 灵敏度= TP / (TP + FN) = 90/(90 + 40) = 69.23%。这意味着 69.23%的脏窗户被正确分类,并从所有非脏窗户中排除。
  • 特异性= TN / (TN + FP) = 60 / (10 + 60) = 85.71%。这意味着 85.71%的非脏窗户被准确分类并排除在脏窗户之外。
  • 准确率= (TP + TN)/(TP + TN + FP + FN) = 75%。这意味着 75%的样本被正确分类。

你会遇到的另一个常用的准确度模型是 F1 评分。它由以下等式给出:

我们可以看到,F1 的分数是召回率和准确率的加权平均值。准确度测量太多了,对吧?这在一开始可能会令人生畏,但随着时间的推移,你会习惯的。

最佳模型选择和评估

模型选择是机器学习算法工作流程中必不可少的一步。但是,模型选择在不同的上下文中有不同的含义:

  • 上下文 1 :在机器学习工作流上下文中,模型选择是选择最佳机器学习算法的过程,如 logistic 回归、SVM、决策树、Random Forest 分类器等等。
  • 上下文 2 :类似的,模型选择阶段也是指针对任意选择的机器学习算法,在不同的超参数之间进行选择的过程。

通常,模型选择是从给定训练数据集的可能候选算法列表中选择一个最佳机器学习算法的方法。有不同的模型选择技术。在正常情况下,我们将训练语料库分成训练集、验证集和测试集。然后,我们在训练集上拟合几个候选模型,使用验证集评估模型,并在测试集上报告模型的性能。然而,只有当我们有足够大的训练语料库时,这种模型选择的场景才起作用。

然而,在许多情况下,用于培训和测试的数据量是有限的。在这种情况下,模型选择变得困难。在这种情况下,我们可以使用两种不同的技术:概率测量重采样方法。如果你想了解这些模型选择技巧,我们建议你继续阅读本章的章节。

模型部署

一旦你得到了基于你的数据集的最佳模型,并且这个模型已经被完全训练好了,是时候部署它了。展示如何将模型完全部署到工作环境中超出了本书的范围。您可以在进一步阅读部分找到足够的资源,为您指明正确的方向。

关于模型部署的主要思想是在真实的工作环境中使用训练好的模型。一旦部署,它应该通过 A/B 用户测试,以便您知道它在真实场景中如何工作。一旦经过全面测试,该应用编程接口就可以向公众开放。

摘要

在这一章中,我们为数据科学奠定了一些基础,了解了可以构建的不同类型的模型,以及如何对它们进行评估。首先,我们讨论了几种监督学习算法,包括回归和分类。然后,我们讨论了无监督学习算法,包括聚类和使用文本数据聚类成不同的聚类使用迷你批处理知识的算法。最后,我们简要讨论了强化学习。

在下一章中,我们将使用到目前为止所学的所有技术对葡萄酒质量数据集执行 EDA。此外,我们将使用监督学习算法对葡萄酒质量进行分类。

进一步阅读

  • 使用 Python 的监督式机器学习泰勒史密斯帕克特出版
  • 使用 Python 进行大规模机器学习巴斯蒂安·萨贾丁卢卡·马萨龙等人。,派克特出版
  • Python 高级机器学习约翰·哈迪帕克特出版
  • 用 Python 进行无监督学习的实践朱塞佩·博纳科尔索帕克特出版
  • 掌握渗透测试的机器学习奇赫布切比派克特出版
  • 动手数据科学与 Python 机器学习弗兰克·凯恩帕克特出版
  • 用 Python 构建机器学习系统-第三版路易斯·佩德罗·科埃略威利·里歇特等人。,*派克特出版**

十一、葡萄酒质量数据 EDA

到目前为止,我们已经讨论了大量关于探索性数据分析 ( EDA )的工具和技术,包括我们如何从不同来源导入数据集,以及如何从数据集中移除异常值,对数据集执行数据分析,并从这样的数据集生成说明性可视化。除此之外,我们还讨论了如何应用高级数据分析,如变量之间的相关性、回归分析和时间序列分析,并基于此类数据集构建高级模型。在本章中,我们将把所有这些技术应用于葡萄酒质量数据集。

本章讨论的主要主题包括以下内容:

  • 公开葡萄酒质量数据集
  • 分析红酒
  • 分析白酒
  • 模型开发和评估
  • 进一步阅读

技术要求

本章的整个代码库可以在CH012文件夹内与本书共享的 GitHub 资源库中找到。本章使用的数据集可以从 UCI 网站(https://archive.ics.uci.edu/ml/datasets/wine+quality)下载,该网站是面向最终用户的开源网站。

我们假设您已经阅读了前面的章节,并且对所需的 Python 库有足够的了解。

公开葡萄酒质量数据集

葡萄酒质量数据集包含葡萄酒各种物理化学特性的信息。整个数据集分为两类:红酒和白酒。每种葡萄酒都有相关的质量标签。标签在 0 到 10 的范围内。在下一节中,我们将下载数据集并将其加载到 Python 中,并执行初步分析以揭示其中的内容。

正在加载数据集

技术要求部分所述,数据集可以直接从 UCI 网站下载。现在,让我们使用 pandas pd.read_csv()方法将数据集加载到 Python 环境中。到目前为止,这个操作应该是相对容易和直观的:

  1. 我们从加载 pandas 库开始,创建两个不同的数据框架,即df_red用于保存红酒数据集,df_white用于保存白酒数据集:
import pandas as pd

df_red = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv", delimiter=";")
df_white = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv", delimiter=";")
  1. 我们创建了两个数据帧。让我们检查可用列的名称:
df_red.columns

此外,这里给出了前面代码的输出:

Index(['fixed acidity', 'volatile acidity', 'citric acid', 'residual sugar',
       'chlorides', 'free sulfur dioxide', 'total sulfur dioxide', 'density',
       'pH', 'sulphates', 'alcohol', 'quality'],
      dtype='object')

如该输出所示,数据集包含以下列:

  • Fixed acidity:表示葡萄酒中酒石酸的含量,单位为克/分 3
  • Volatile acidity:表示酒中醋酸的量。以 g/dm 3 计量。
  • Citric acid:表示酒中柠檬酸的量。也以 g/dm 3 计量。
  • Residual sugar:表示发酵过程完成后,葡萄酒中剩余的糖量。也以 g/dm 3 计量。
  • Free sulfur dioxide:测量游离形式的二氧化硫(SO 2 )的量。也以 g/dm 3 计量。
  • Total sulfur dioxide:测量酒中 SO 2 的总量。这种化学物质作为抗氧化剂和抗菌剂。
  • Density:表示酒的密度,单位为克/分 3
  • pH:表示酒的 pH 值。值的范围在 0 到 14.0 之间,表示酸度非常高,14 表示碱性酸度。
  • Sulphates:表示酒中硫酸钾的量。也以 g/dm 3 计量。
  • Alcohol:表示酒中的酒精含量。
  • Quality:表示酒的质量,范围从 1 到 10。在这里,价值越高,酒越好。

讨论了数据集中的不同列后,现在让我们在下一节中查看数据的一些基本统计信息。

描述性统计学

让我们看看红酒数据框中的一些样本数据。请记住,我们可以使用不同的方法来查看数据框中的数据,包括pd.head()pd.tail()pd.iloc():

  1. 在这里,我将检查第 100 和第 110 之间的条目:
df_red.iloc[100:110]

这里给出了前面代码的输出:

Figure 12.1 - Display the entries from the 100th to 110th rows from the red wine dataframe

  1. 除此之外,我们还可以看到每一列的数据类型。让我们使用这里给出的片段:
df_red.dtypes

前面代码的输出如下:

fixed acidity float64
volatile acidity float64
citric acid float64
residual sugar float64
chlorides float64
free sulfur dioxide float64
total sulfur dioxide float64
density float64
pH float64
sulphates float64
alcohol float64
quality int64
dtype: object

如前面的输出所示,除了quality列是int64之外,大部分列都是float64格式。

  1. 我们还可以描述数据帧以获得更多描述性信息。你还记得这样做的方法的名字吗?当然,我们使用pd.describe()方法。查看代码片段:
df_red.describe()

这里给出了前面代码的输出:

Figure 12.2 - Output of the described method

注意图 12.2pd.describe()方法的输出,表示每一列都有相同的条目数,1,599,显示在行计数中。现在,每一行和每一列的值都应该有意义了。如果你还不明白,我们强烈建议修改第五章 、描述性统计

数据争论

嗯,图 12.2 显示每一列都有相同的项目数,说明没有遗漏值。

我们可以使用这里显示的pd.info()方法来验证:

df_red.info()

给出了前面代码的输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1599 entries, 0 to 1598
Data columns (total 12 columns):
fixed acidity 1599 non-null float64
volatile acidity 1599 non-null float64
citric acid 1599 non-null float64
residual sugar 1599 non-null float64
chlorides 1599 non-null float64
free sulfur dioxide 1599 non-null float64
total sulfur dioxide 1599 non-null float64
density 1599 non-null float64
pH 1599 non-null float64
sulphates 1599 non-null float64
alcohol 1599 non-null float64
quality 1599 non-null int64
dtypes: float64(11), int64(1)
memory usage: 150.0 KB

如前面的输出所示,所有列都没有空值。由于没有空条目,我们不需要处理丢失的值。假设有一些,那么我们将使用我们在第 4 章数据转换中概述的技术来处理它们。

We can also access the data quality and missing values using the ways shown in Chapter 4Data Transformation. We can use the pandas method, df_red.isnull().sum().

既然知道不需要进一步的数据转换步骤,我们就在下一节回顾一下红酒的数据分析。

分析红酒

在本节中,我们将继续分析红酒数据集。首先,我们将从探索最相关的列开始。其次,我们将比较两个不同的列并观察它们的列。

我们先从quality栏开始:

import seaborn as sns

sns.set(rc={'figure.figsize': (14, 8)})
sns.countplot(df_red['quality'])

这里给出了前面代码的输出:

Figure 12.3 - The output indicates that the majority of wine is of medium quality

这并不难,不是吗?正如我一直认为的,当你有一个图表时,最重要的一个方面是能够解释结果。如果勾选图 12.3 ,可以看到大部分红酒属于质量标签 3 和 4 的那一组,其次是标签 5 和 6,部分红酒属于标签 7 的那一组,以此类推。

查找相关列

接下来让我们找出红酒数据库中哪些列是高度相关的。如果你还记得,我们在第 7 章中讨论了不同类型的相关性。为了让你把握关联背后的意图,我强烈推荐你去翻看第七章关联,只是为了重整一下记忆。说到这里,让我们继续寻找高度相关的列:

  1. 我们可以继续使用seaborn.pairplot()方法,如下图所示:
sns.pairplot(df_red)

您应该会得到一个非常全面的图表,如屏幕截图所示:

Figure 12.4 - Correlation between different columns of the red wine dataframe

前面的屏幕截图显示了每对可能的组合列的分散图。该图说明了固定酸度和密度之间的一些正相关关系。酸度与 pH 呈负相关,同样,酒精百分比与密度也呈负相关。此外,您可以准确地看到哪些列与其他列具有正相关或负相关。然而,由于pairplot图没有数字,解释结果可能会有点偏差。例如,检查固定酸度和挥发性酸度的色谱柱之间的相关性。这个图可能是对称的。然而,你可能会说在图的右边有一些稀疏的点,所以有轻微的负相关。在这里,我的观点是,没有任何具体的可量化的数字,很难讲。这就是为什么我们可以用sns.heatmap()方法来量化相关性的原因。

  1. 我们可以生成heatmap图,如下图所示:
sns.heatmap(df_red.corr(), annot=True, fmt='.2f', linewidths=2)

它产生的输出如下:

Figure 12.5 - Heatmap showing the correlation between different columns

图 12.5 描绘了不同列之间的相关性。由于我们关注的是质量柱,因此质量柱与酒精、硫酸盐、残糖、柠檬酸和固定酸度呈正相关。因为有数字,所以很容易看出哪些列是正相关的,哪些列是负相关的。

图 12.5 是否可以得出以下结论:

  • 酒精与红酒的质量呈正相关。
  • 酒精与 pH 值呈弱正相关。
  • 柠檬酸和密度与固定酸度呈强正相关。
  • 酸碱度与密度、固定酸度、柠檬酸和硫酸盐呈负相关。

图 12.5 的热图中我们可以得出几个结论。此外,我们必须认识到相关性的重要性,以及在数据科学模型开发过程中,相关性如何帮助我们确定特征集。

A column has a perfect positive correlation with itself. For example, the quality of wine has a positive correlation with itself. This is the reason why all of the diagonal elements have a positive correlation of 1.

我们可以进一步深入到单个列并检查它们的分布。比方说,我们想看看酒精浓度相对于红酒质量的分布情况。首先,让我们绘制分布图,如下所示:

sns.distplot(df_red['alcohol'])

前面代码的输出如下:

Figure 12.6 - Alcohol distribution graph

图 12.6 可以看出,酒精分布与红酒品质呈正相关。我们可以使用 scipy.stats 中的skew方法来验证这一点。

from scipy.stats import skew
skew(df_red['alcohol'])

前面代码的输出如下:

0.8600210646566755

输出证实酒精是正向倾斜的。这让我们对酒精一栏有了更深入的了解。

请注意,我们可以验证每一列,并尝试查看它们相对于另一列的偏斜度、分布和相关性。这通常是必要的,因为我们正在经历特征工程的过程。

酒精与质量

让我们看看葡萄酒的质量是如何随着酒精浓度而变化的。这可以使用方框图来完成。检查这里给出的代码片段:

sns.boxplot(x='quality', y='alcohol', data = df_red)

前面代码的输出如下:

Figure 12.7 - A box plot showing the variation of the quality of wine with respect to alcohol concentration

注意图 12.7 中的方框,显示了图外的一些点。这些都是异常值。图 12.7 所示的大部分异常值都在品质 5 和 6 的葡萄酒附近。我们可以通过传递参数showoutliers=False来移除异常值,如以下代码所示:

sns.boxplot(x='quality', y='alcohol', data = df_red, showfliers=False)

代码的输出更加清晰,如下所示:

Figure 12.8 -A box plot showing the variation of the quality of wine with respect to alcohol concentration without outliers

注意,从图 12.8 来看,似乎随着酒质的提升,酒精浓度也随之提升。有道理,对吧?酒精浓度越高,酒的质量就越高。

酒精对酸碱度

接下来,我们也来看看酒精column与 pH 值的相关性。从图 12.5 我们已经知道它们是弱正相关的。让我们验证本节中的结果:

  1. 首先,我们来看看联合剧情:
sns.jointplot(x='alcohol',y='pH',data=df_red, kind='reg')

前面的代码现在对您来说应该不是新的。我们已经在第 2 章、【EDA 中的 T2】视觉辅助、第 7 章关联中讨论过这些情节的意义。由前面的代码生成的图表显示在屏幕截图中:

Figure 12.9 - Joint plot illustrating the correlation between alcohol concentration and the pH values

这张截图显示,酒精与酸碱度呈弱正相关。此外,在截图中描绘了回归线,说明了它们之间的相关性。

  1. 我们可以从scipy.stats开始使用皮尔逊回归来量化相关性,如下所示:
from scipy.stats import pearsonr

def get_correlation(column1, column2, df):
  pearson_corr, p_value = pearsonr(df[column1], df[column2])
  print("Correlation between {} and {} is {}".format(column1, column2, pearson_corr))
  print("P-value of this correlation is {}".format(p_value))
  1. 我们可以使用前面的方法来查看任意两列之间的相关性。让我们看看alcoholpH的相关性:
get_correlation('alcohol','pH', df_red)

前面代码的输出如下所示:

Correlation between alcohol and pH is 0.20563250850549825
P-value of this correlation is 9.96449774146556e-17

注意,这与图 12.5 中所示的数值大致相同。现在,您知道了不同的方法,可以检查两个或多个列的关联有多强或多弱。

在下一节中,我们将分析白葡萄酒数据框架,并将其与红葡萄酒进行比较。

分析白酒

在这一节中,我们将分析白酒,并将其与前一节中的红酒分析进行比较。让我们从加载白葡萄酒数据框开始:

df_white = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv", delimiter=";")

该代码将白葡萄酒数据集加载到df_white数据框中。

红酒对白酒

我们的输出类是quality列。基于该栏,我们可以尝试找到每种葡萄酒的平均质量,如下所示:

print("white mean = ",df_white["quality"].mean())
print("red mean =",df_red["quality"].mean())

代码的输出如下:

white mean = 5.87790935075541
red mean = 5.6360225140712945

正如产量所说,白葡萄酒的平均质量为 5.877,红葡萄酒的平均质量为 5.63。两个数据框中的列相同。

添加新属性

让我们向两个数据帧添加一个新属性wine_category。你记得我们是怎么做到的吗?

当然,请检查下面给出的示例代码:

df_white['wine_category'] = 'white'
df_red['wine_category'] = 'red'

很简单,对吧?接下来,让我们看看这两种葡萄酒的柱质量有何独特价值:

print('RED WINE: List of "quality"', sorted(df_red['quality'].unique()))
print('WHITE WINE: List of "quality"', sorted(df_white['quality'].unique()))

前面代码的输出如下所示:

RED WINE: List of "quality" [3, 4, 5, 6, 7, 8]
WHITE WINE: List of "quality" [3, 4, 5, 6, 7, 8, 9]

请注意,红葡萄酒和白葡萄酒在质量栏中具有相同的唯一值。

转换成分类列

虽然质量一栏是数字的,但是在这里,我们感兴趣的是把质量作为类。为了清楚起见,让我们在本小节中将数值转换为分类值。

为此,我们需要一套规则。让我们定义一组规则:

听起来可行,对吧?当然是。让我们检查代码,如下所示:

df_red['quality_label'] = df_red['quality'].apply(lambda value: ('low' if value <= 5 else 'medium') if value <= 7 else 'high')
df_red['quality_label'] = pd.Categorical(df_red['quality_label'], categories=['low', 'medium', 'high'])

df_white['quality_label'] = df_white['quality'].apply(lambda value: ('low' if value <= 5 else 'medium') if value <= 7 else 'high')
df_white['quality_label'] = pd.Categorical(df_white['quality_label'], categories=['low', 'medium', 'high'])

到目前为止,前面的代码应该是不言自明的。我们只是使用pandas.apply()方法来检查quality列中的值。根据它们的价值,如果它们小于或等于 5,我们将其归类为低质量葡萄酒。同样,如果quality列的值大于 5 且小于或等于 7,我们将其归类为中等品质葡萄酒。最后,任何列的quality值大于 7 的行都被归类为优质葡萄酒。

让我们来数一数每一类葡萄酒的价值:

print(df_white['quality_label'].value_counts())
df_red['quality_label'].value_counts()

前面代码的输出如下所示:

medium 3078
low 1640
high 180
Name: quality_label, dtype: int64

medium 837
low 744
high 18
Name: quality_label, dtype: int64

上面的是白酒,下面的是红酒。从前面的输出可以非常明显地看出,在这两种情况下,大多数葡萄酒都是中等质量的。

连接数据帧

让我们对这两种类型的数据帧进行联合探索。你还记得我们如何合并数据帧吗?如果没有,我强烈建议在这里暂停一下,快速浏览一下第 6 章**分组数据集:

*1. 让我们看看如何连接这两个数据帧:

df_wines = pd.concat([df_red, df_white])
  1. 让我们重新洗牌,使数据点随机化:
df_wines = df_wines.sample(frac=1.0, random_state=42).reset_index(drop=True)

请注意,drop=True参数将索引重置为默认的整数索引。

  1. 接下来,我们要检查前几列,看看是否所有的行都正确合并了:
df_wines.head()

前面代码的输出如下所示:

Figure 12.10 - Output of the df.head(10) code snippet shown earlier

注意在图 12.10 中,我们已经正确填充了列wine_categoryquality_label

分组列

我们已经在第 6 章分组数据集中讨论了使用 Pandas 数据框对列和行进行分组的几种方法。在本节中,我们将使用相同的技术将不同的列组合在一起:

  1. 让我们使用组合数据框,并使用列alcoholdensitypHquality对它们进行分组。
  2. 接下来,我们可以应用pd.describe()方法获得最常用的描述性统计:
subset_attr = ['alcohol', 'density', 'pH', 'quality']

low = round(df_wines[df_wines['quality_label'] == 'low'][subset_attr].describe(), 2)
medium = round(df_wines[df_wines['quality_label'] == 'medium'][subset_attr].describe(), 2)
high = round(df_wines[df_wines['quality_label'] == 'high'][subset_attr].describe(), 2)

pd.concat([low, medium, high], axis=1, 
          keys=[' Low Quality Wine', 
                ' Medium Quality Wine', 
                ' High Quality Wine'])

在前面的代码片段中,首先,我们创建了我们感兴趣的属性子集。然后,我们为低品质葡萄酒、中品质葡萄酒和高品质葡萄酒创建了三个不同的数据框。最后,我们将它们串联起来。这里给出了前面代码的输出:

Figure 12.11 - Output of grouping the columns and performing the describe operation

如前面的截图所示,我们将数据集分为三个不同的组:低品质葡萄酒、中品质葡萄酒和高品质葡萄酒。每组显示三种不同的属性:酒精、密度和酸碱度。在数据分析阶段,使用串联方法根据特定条件对列进行分组非常方便。

在下一节中,我们将讨论葡萄酒质量数据集的单变量分析。

单变量分析

我们已经在第 7 章相关性中讨论了单变量、双变量和多变量分析。让我们复习一下,看看你还记得多少。

可视化数字数据及其分布的最简单方法是使用直方图。让我们在这里绘制直方图;我们从导入所需的matplotlib.pyplot库开始:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

接下来,我们绘制直方图,如图所示:

fig = df_wines.hist(bins=15, color='fuchsia', edgecolor='darkmagenta', linewidth=1.0, xlabelsize=10, ylabelsize=10, xrot=45, yrot=0, figsize=(10,9), grid=False)

plt.tight_layout(rect=(0, 0, 1.5, 1.5)) 

请注意,我们使用了tight_layout()方法来保持图形的组合。

You can get the list of all matplotlib color codes from the official website, at https://matplotlib.org/examples/color/named_colors.html.

前面代码的输出如下所示:

Figure 12.12 - Output of the univariate analysis

前面的截图显示了每个变量/列及其在组合数据框中的分布。到目前为止,结果图应该是不言自明的。

组合数据帧的多元分析

让我们使用组合数据集执行多元分析。我们将使用相同的热图图来执行多元分析:

  1. 让我们从创建图形开始。首先,我们创建一个子场景:
fig, (ax) = plt.subplots(1, 1, figsize=(14,8))
  1. 接下来,我们创建热图,如下所示:
hm = sns.heatmap(df_wines.corr(), 
                ax=ax, 
                 cmap="bwr", 
                 annot=True, 
                 fmt='.2f', 
                 linewidths=.05)
  1. 最后,让我们绘制子情节,并用合适的标题填充它:
fig.subplots_adjust(top=0.93)
fig.suptitle('Combined Wine Attributes and their Correlation Heatmap', fontsize=14, fontweight='bold')

前面代码的输出如下所示:

Figure 12.13 - A heatmap illustrating correlation between several columns

注意前面的截图类似于图 12.5,应该用同样的方式解读。在这种情况下,唯一的区别是我们对组合数据帧进行了多元分析。

*# 离散分类属性

我们的数据框中有一个离散的分类列wine_category

让我们使用seaborn库使用计数图来可视化它:

fig = plt.figure(figsize=(16, 8))

sns.countplot(data=df_wines, x="quality", hue="wine_category") 

前面代码的输出如下所示:

Figure 12.14 - Visualizing the discrete categorical dataset

图 12.14 显示了葡萄酒的不同类别[3、4、5、6、7、8、9]以及它们在一个漂亮的计数图上的频率分布。对于最终利益相关者来说,这是一个更清晰的说明。

三维可视化

一般来说,我们从一维可视化开始,然后进入更深的维度。之前看过二维可视化,让我们再增加一个维度,绘制三维图表。我们将使用matplotlib.pyplot方法来实现:

  1. 让我们首先创建轴:
fig = plt.figure(figsize=(16, 12))
ax = fig.add_subplot(111, projection='3d')
  1. 然后,将列添加到轴上:
xscale = df_wines['residual sugar']
yscale = df_wines['free sulfur dioxide']
zscale = df_wines['total sulfur dioxide']
ax.scatter(xscale, yscale, zscale, s=50, alpha=0.6, edgecolors='w')

在这里,我们有兴趣研究残余糖、游离二氧化硫和总二氧化硫柱。

  1. 最后,让我们将标签添加到所有轴上:
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('free sulfur dioxide')
ax.set_zlabel('Total sulfur dioxide')

plt.show()

前面代码的输出如下所示:

Figure 12.14 - 3-D plot illustrating the correlation between three different columns

图 12.14 显示三个变量相互之间呈正相关。在前面的例子中,我们使用了 Matplotlib 库。我们也可以使用seaborn库绘制三个不同的变量。检查这里给出的代码片段:

fig = plt.figure(figsize=(16, 12))

plt.scatter(x = df_wines['fixed acidity'], 
            y = df_wines['free sulfur dioxide'], 
            s = df_wines['total sulfur dioxide'] * 2,
            alpha=0.4, 
            edgecolors='w')

plt.xlabel('Fixed Acidity')
plt.ylabel('free sulfur dioxide')
plt.title('Wine free sulfur dioxide Content - Fixed Acidity - total sulfur dioxide', y=1.05)

请注意,我们使用了s参数来表示第三个变量(二氧化硫总量)。代码的输出如下:

Figure 12.16 - Plot illustrating three different variables as shown in the preceding code

注意在图 12.16 中,圆圈的大小表示第三个变量。在这种情况下,圆的半径越大,残糖的价值越高。因此,如果你仔细观察,你会发现大部分较高的圆位于值为 4 到 10 的 x 轴和值为 25 到 150 的 y 轴之间。

在下一节中,我们将开发不同类型的模型,并应用一些经典的机器学习 ( ML )算法,并评估它们的性能。

模型开发和评估

在本节中,我们将开发不同类型的经典 ML 模型,并评估它们的性能。我们已经在 第 9 章假设检验和回归第 10 章模型开发和评估中详细讨论了模型的开发和评估。在这里,我们将直接进入实现。

我们将使用以下不同类型的算法并评估它们的性能:

  • 逻辑回归
  • 支持向量机
  • k 近邻分类器
  • 随机森林分类器
  • 决策树分类器
  • 梯度提升分类器
  • 高斯朴素贝叶斯分类器

虽然深入研究每个分类器超出了本章和本书的范围,但我们在这里的目的是介绍如何在对某些数据库执行 EDA 操作后继续开发 ML 算法:

  1. 让我们首先导入所需的库:
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC,SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier,GradientBoostingClassifier,AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split,cross_validate
from sklearn.preprocessing import MinMaxScaler,StandardScaler,LabelEncoder
from sklearn.metrics import accuracy_score,precision_score,recall_score,f1_score 

请注意,我们将使用组合的数据帧。接下来,我们将对quality_label列的分类值进行编码。我们将对这些值进行编码,以便所有低值都变为 0,中值变为 1,高值变为 2。

  1. 让我们执行编码:
label_quality = LabelEncoder()

df_wines['quality_label'] = label_quality.fit_transform(df_wines['quality_label'])

这并不难,对吧?我们刚刚利用了sklearn预处理功能提供的LabelEncoder实用程序功能。

  1. 现在,让我们将数据集分成训练集和测试集。我们将使用 70%的数据集作为训练集,剩余的 30%作为测试集:
x_train,x_test,y_train,y_test=train_test_split(df_wines.drop(['quality','wine_category'],axis=1),df_wines['quality_label'],test_size=0.30,random_state=42)

我们使用了sklearn库提供的train_test_split()方法。请注意前面类别中的以下内容:

  • 在前面的代码中,我们不再需要qualitywine_category列,所以我们删除了它们。
  • 接下来,我们以 30%的数据作为测试集。我们可以通过简单地传递test_size = 0.30参数来做到这一点。
  1. 接下来,我们创建模型。请注意,我们可以为上面列出的每个算法单独构建模型。取而代之的是,我们将列出它们,并对每一个进行循环,计算精度。检查下面给出的代码片段:
models=[LogisticRegression(),
        LinearSVC(),
        SVC(kernel='rbf'),
        KNeighborsClassifier(),
        RandomForestClassifier(),
        DecisionTreeClassifier(),
        GradientBoostingClassifier(),
        GaussianNB()]

model_names=['LogisticRegression','LinearSVM','rbfSVM', 'KNearestNeighbors', 'RandomForestClassifier', 'DecisionTree', 'GradientBoostingClassifier', 'GaussianNB']
  1. 接下来,我们将循环每个模型,创建一个模型,然后评估准确性。检查下面给出的代码片段:
acc=[]
eval_acc={}

for model in range(len(models)):
    classification_model=models[model]
    classification_model.fit(x_train,y_train)
    pred=classification_model.predict(x_test)
    acc.append(accuracy_score(pred,y_test))

eval_acc={'Modelling Algorithm':model_names,'Accuracy':acc}
eval_acc

这里给出了前面代码的输出:

{'Accuracy': [0.9687179487179487,
  0.9733333333333334,
  0.6051282051282051,
  0.6912820512820513,
  1.0,
  1.0,
  1.0,
  1.0],
 'Modelling Algorithm': ['LogisticRegression',
  'LinearSVM',
  'rbfSVM',
  'KNearestNeighbors',
  'RandomForestClassifier',
  'DecisionTree',
  'GradientBoostingClassifier',
  'GaussianNB']}
  1. 让我们创建一个精确度的数据框架,并将其显示在条形图中:
acc_table=pd.DataFrame(eval_acc)
acc_table = acc_table.sort_values(by='Accuracy', ascending=[False])
acc_table

前面代码的输出如下所示:

Figure 12.17 - Accuracy dataframe of different algorithms

请注意,将质量转换为分类数据集给了我们更高的准确性。大多数算法给出了 100%的准确性,如前面的截图所示。

  1. 让我们创建一个条形图:
sns.barplot(y='Modelling Algorithm',x='Accuracy',data=acc_table)

前面代码的输出如下所示:

Figure 12.18 - Different types of algorithms and their accuracies

注意,如截图所示,随机森林、决策树、梯度提升分类器和高斯朴素贝叶斯分类器都给出了 100%的准确率。

太好了。恭喜你,你已经成功完成了主要项目。请注意,本书中所有的代码、片段和方法都是为了提供解决特定问题的最少方法。总有一种方法可以让你进行更深入的分析。我们鼓励您阅读每章的进一步阅读部分,以获得特定领域的高级知识。

摘要

在本章中,我们使用了 UCI 提供的葡萄酒质量数据集来执行 EDA。我们讨论了如何执行 EDA 技术,例如数据加载、数据争论、数据转换、变量之间的相关性、回归分析以及基于数据集构建经典的 ML 模型。

这是这本书的最后一章。如前所述,本书提供的理论、代码和插图的解释是为了给你提供一个基础知识集。我们假设读完这本书后,你将获得足够的洞察力、技巧和技能,使它更上一层楼。

进一步阅读

  • Python 有监督机器学习泰勒·史密斯帕克特出版,2019 年 5 月 26 日
  • 使用 Python 进行大规模机器学习Bastian SjardinLuca Massaron 、e t al。packkt**发布,2016 年 8 月 2 日
  • Python 高级机器学习约翰·哈迪帕克特 出版,2016 年 7 月 27 日
  • Python 的动手无监督学习Giuseppe BonaccorsoPackt**出版,2019 年 2 月 28 日
  • 掌握渗透测试的机器学习奇赫布切比帕克特 出版,2018 年 6 月 26 日
  • 动手数据科学与 Python 机器学习弗兰克·凯恩帕克特 出版,2017 年 7 月 30 日
  • 用 Python 构建机器学习系统–第三版路易斯·佩德罗·科埃略威利·里歇特、e t al。packkt**发布,2018 年 7 月 30 日
  • 葡萄酒质量数据集归属于 P. CortezA. CerdeiraF. AlmeidaT. MatosJ. Reis建模 w 通过从理化性质中进行数据挖掘来确定偏好。在决策支持系统,爱思唯尔,47(4):547-553,2009。**

十二、附录

如前所述,数据预处理和数据转换是数据挖掘和其他数据科学方法中最重要的两个过程。在数据处理阶段,我们的数据通常是字符串形式的。互联网上的大多数数据集都是基于字符串的。因此,管柱操纵技术是探索性数据分析 ( EDA )的重要组成部分。

在本附录章节中,我们将了解以下主题:

  • 字处理
  • 使用 Pandas 向量化字符串函数
  • 使用正则表达式

字处理

对于字符串操作,我们指的是如何创建字符串、访问这些字符串中的字符、分割字符串、删除或更新字符串中的字符以及其他字符串操作符。在接下来的部分中,我们将一个接一个地看到所有这些步骤。

创建字符串

我们可以用三种不同的方式在 Python 中创建字符串:

  • 使用单引号
  • 使用双引号
  • 使用三重引号。

请看下面的例子:

String1 = 'Creating a String with single Quotes.'
String2 = "Creating a String with double Quotes."
String3 = '''Creating a String with triple Quotes.'''
print(String1) 
print(String2) 
print(String3) 

所有三个print语句的输出都是相同的。他们按照预期创建了一个字符串。

在 Python 中访问字符

Python 提供了一种索引机制来访问任何字符串中的任何字符。任何字符串的索引都以 0 开头。我们还可以使用负索引值从任何字符串的后面访问任何字符。例如,-1 表示字符串的最后一个字符,-2 表示倒数第二个字符,依此类推。请注意,如果我们尝试访问任何不在字符串限制内的索引,Python 会以TypeError提醒我们。

下面是用于访问字符串字符的 Python 代码:

# characters of String 
String = "Exploratory Data Analysis"

# Printing First character 
print("\nFirst character of String is: ") 
print(String[0]) 

# Printing Last character 
print("\nLast character of String is: ") 
print(String[-1])

每个代码块的输出都显示有一个内联注释,这样更容易理解。

字符串切片

要访问字符串中的一系列字符,可以使用切片方法。使用切片操作符(:)对字符串进行切片。这里有一个演示字符串切片的程序:

# Creating a String 
String = "Exploratory Data Analysis"

# Outputs: Slicing characters from 3-12:loratory 
print("\nSlicing characters from 3-12: ") 
print(String[3:12]) 

# Outputs:Slicing characters between 3rd and 2nd last character: loratory Data Analys 
print("\nSlicing characters between " + "3rd and 2nd last character: ") 
print(String[3:-2]) 

每个代码块的输出都显示有一个内联注释,这样更容易理解。

从字符串中删除/更新

Python 不支持字符串变异。也就是说,它不允许删除或更新字符串中的任何字符。如果我们试图这样做,就会产生一个错误。但是,使用内置的del关键字可以删除整个字符串。在字符串更新的情况下出现错误的主要原因是字符串是不可变的。在下面的代码块中,让我们创建一个字符串,并尝试更新其中的一些字符,如下所示:

# Updation of a character

String = "Exploratory Data Analysis"

String[2] = 'p'
print("\nUpdating character at 2nd Index: ") 
print(String) 

前面的代码应该会给你一个错误。

Python 中的转义序列

一个字符串的语法中已经包含了单引号(')和双引号('')。因此,如果我们需要在一个字符串中打印一个单引号或一个双引号,就会产生一个SyntaxError。为了避免这种错误,必须对引号(无论是单引号还是双引号)进行转义。这种现象被称为逃逸序列。转义序列以反斜杠(\)开头,可以有不同的理解。如果我们打算使用单引号或双引号作为字符串,那么它必须通过在前面附加一个反斜杠来转义。让我们看看它在行动。

我们将针对所有三种情况(单引号、双引号和三引号)显示以下示例:

String = '''I'm a "Data Scientist"'''

# Initial String 
print("Initial String with use of Triple Quotes: ") 
print(String) 

# Escaping Single Quote 
String = 'I\'m a "Data Scientist"'
print("\nEscaping Single Quote: ") 
print(String) 

# Escaping Double Quotes 
String = "I'm a \"Data Scientist\""
print("\nEscaping Double Quotes: ") 
print(String) 

在计算机科学中,我们经常需要在不同的场合提供一些文件或数据集的路径。处理数据科学时也是如此。第一步是加载数据集。为此,我们必须使用双斜线提供文件的链接或路径,以便转义双斜线。然后,我们用转义序列打印路径,如下例所示:

String = "C:\\Python\\DataScience\\"
print("\nEscaping Backslashes: ") 
print(String)

上述代码生成以下输出:

Escaping Backslashes: 
C:\Python\DataScience\

注意代码中双斜线的使用——这提供了单斜线作为输出。这就是为什么逃跑是一种非常有用的机制。

格式化字符串

我们可以使用format()方法在 Python 中格式化字符串。当以任何特定格式显示输出时,这种方法非常灵活和强大。format()方法将大括号{}作为占位符,可以根据特定的顺序被任何特定的参数替换。看看下面这些例子。

让我们首先看一个默认订单的例子:

# Default order 
String1 = "{} {} {}".format('Exploratory ', 'Data ', 'Analysis') 
print("Print String in default order: ") 
print(String1)

前面代码的输出如下:

Print String in default order:
Exploratory Data Analysis

除了这个默认顺序,我们还可以使用位置格式。比如你有一个('Exploratory', 'Data', 'Analysis')这样的字符串,我们要显示一个('Data', 'Exploratory', 'Analysis')字符串。我们可以通过使用位置格式来实现这一点,如下例所示:

# Positional Formatting 
String1 = "{1} {0} {2}".format('Exploratory', 'Data', 'Analysis') 
print("\nPrint String in Positional order: ") 
print(String1) 

我们也可以通过使用关键字来格式化任何字符串。例如,看看下面的代码:

# Keyword Formatting 
String1 = "{l} {f} {g}".format(g = 'Exploratory', f = 'Data', l = 'Analysis') 
print("\nPrint String in order of Keywords: ") 
print(String1) 

前面代码的输出如下:

Print String in order of Keywords: 
Analysis Data Exploratory

接下来,我们将了解如何加载文本数据集并执行预处理操作。

使用 Pandas 向量化字符串函数

对于字符串格式,最好使用稍微混乱一点的数据集。我们将在撰写综述论文时使用我在博士研究期间收集的数据集。可以在这里找到:https://raw . githubusercontent . com/sureshHARDIYA/PhD-resources/master/Data/Review % 20 paper/preferred . CSV

  1. 让我们加载这篇文本文章,然后显示前八个条目。让我们从加载数据并检查其结构和一些注释开始,如下所示:
import numpy as np
import pandas as pd
import os
  1. 接下来,让我们阅读文本文件并显示最后的10项,如下所示:
text = pd.read_csv("https://raw.githubusercontent.com/sureshHARDIYA/phd-resources/master/Data/Review%20Paper/preprocessed.csv")
text = text["TITLE"] 
print (text.shape)
print( text.tail(10))
  1. 前面代码的输出可以在下面的截图中看到:

Figure 1: This is the output of the preceding code

Pandas 扩展了操作整个字符串系列的内置功能。在下一节中,我们将对 pandas 字符串函数使用相同的数据集。

对 Pandas 数据框使用字符串函数

让我们使用 Pandas 数据框的内置函数。我们将继续使用上一节中导入的相同数据集。Python 中的大多数字符串操作函数都使用 Pandas 向量化字符串方法。

以下是 Pandas 字符串函数的列表,这些函数反映了 Python 字符串方法:

Figure 2 - List of vectorized string functions in pandas

让我们练习以下用例:

  1. 从数据框的text列中提取第一句,并将其转换为小写字符,如下所示:
 text[0].lower() 
  1. text列中的所有注释转换为小写,并显示前八个条目,如下所示:
text.str.lower().head(8)
  1. 提取第一句并将其转换为大写字符,如下所示:
text[0].upper() 
  1. 获取文本字段中每个注释的长度,并显示前八个条目,如下所示:
text.str.len().head(8)
  1. 将所有注释组合成一个字符串,并显示前 500 个字符,如下所示:
text.str.cat()[0:500]  

明智的做法是验证所有的注释都连接在一起。你能想到我们可能需要将所有注释组合成一个字符串的用例吗?好吧,不如——比如说——我们希望看到所有用户在评论时最常选择的词语。

  1. 将每个字符串分割成一个系列,并使用series.str.slice()以元素方式返回结果,如以下代码片段所示:
text.str.slice(0, 10).head(8)  
  1. 使用str.replace()将给定子串的出现替换为不同的子串,如以下代码片段所示:
text.str.replace("Wolves", "Fox").head(8)

在前面的例子中,Wolves的所有情况将被替换为Fox。这充当了一个搜索和替换的功能,你可以在许多内容管理系统和编辑器中找到。

在处理文本数据时,我们经常测试字符串是否包含特定的子字符串或字符模式。让我们只搜索那些提到安德鲁·威金斯的评论。我们需要匹配所有提到他的帖子,避免匹配没有提到他的帖子。

使用series.str.contains()获取一系列真/假值,指示每个字符串是否包含给定的子字符串,如下所示:

# Get first 10 comments about Andrew Wiggins
selected_comments = text.str.lower().str.contains("wigg|drew")

text[selected_comments].head(10) 

仅供参考,让我们计算一下提到安德鲁·威金斯的评论比例,如下所示:

len(text[selected_comments])/len(text)

输出是 0.06649063850216035。可以看到,6.6%的评论都提到了安德鲁·威金斯。这是我们提供给str.contains()的字符串模式参数的输出。

关于安德鲁·威金斯的帖子可以用任何不同的名字来指代他——维金斯、安德鲁、维格、德鲁——所以我们需要比单个子字符串更灵活的东西来匹配我们感兴趣的所有帖子。我们提供的模式是正则表达式的一个简单示例。

使用正则表达式

正则表达式(regex)是一系列字符和特殊元字符,用于匹配一组字符串。正则表达式允许您在字符串匹配操作中比仅仅提供简单的子字符串更具表现力。你可以把它想象成一个模式,你想用不同长度的字符串进行匹配,由不同的字符组成。

str.contains()方法中,我们提供了正则表达式wigg|drew。在这种情况下,竖线|是充当OR运算符的元字符,因此该正则表达式匹配包含子字符串wiggdrew的任何字符串。

元字符允许您更改匹配方式。当您提供不包含元字符的正则表达式时,它只匹配精确的子字符串。例如,Wiggins将只匹配包含精确子串Wiggins的字符串。

下面是基本元字符的列表,以及它们的作用:

  • ".":句点是匹配除换行符以外的任何字符的元字符,如下面的代码块所示:
# Match any substring ending in ill
my_words = pd.Series(["abaa","cabb","Abaa","sabb","dcbb"])

my_words.str.contains(".abb")
  • "[ ]":方括号指定一组要匹配的字符。查看以下示例片段,并将您的输出与本章中给出的笔记本进行比较:
my_words.str.contains("[Aa]abb")
  • "^":在方括号外,插入符号在字符串的开头搜索匹配项,如下面的代码块所示:
Sentence_series= pd.Series(["Where did he go", "He went to the shop", "he is good"])

Sentence_series.str.contains("^(He|he)") 
  • "( )":正则表达式中的括号用于分组和强制执行正确的操作顺序,就像它们用于数学和逻辑表达式一样。在前面的例子中,括号让我们对 OR 表达式进行分组,以便"^""$"符号对整个OR语句进行操作。
  • "*":星号匹配前面字符的0个或多个副本。
  • "?":问号匹配前面字符的01副本。
  • "+":加号匹配前面字符的 1 个或多个副本。
  • "{ }":花括号匹配指定重复次数的前一个字符:
  • "{m}":前面的元素匹配m次。
  • "{m,}":前面的元素匹配m次以上。
  • "{m,n}":前一个元素在mn时间之间匹配。

正则表达式包括几个特殊的字符集,允许我们快速指定某些常见的字符类型。它们包括以下内容:

  • 在方括号内添加"^"符号匹配集合中的任何字符而不是:
  • Python 正则表达式还包括一个指定常见序列的简写:
  • \d:匹配任意数字。
  • \D:匹配任何非数字。
  • \w:匹配一个单词字符。
  • \W:匹配一个非单词字符。
  • \s:匹配空白(空格、制表符、换行符等。).
  • \S:匹配非空白。

请记住,我们确实在字符串格式化时逃脱了排序。同样,当您想要匹配元字符符号本身时,您必须在元字符中用""转义。

例如,如果你想匹配句点,你不能使用".",因为它是一个匹配任何东西的元字符。相反,您可以使用.来转义句点的元字符行为,并匹配句点本身。下面的代码块说明了这一点:

# Match a single period and then a space

Word_series3 = pd.Series(["Mr. SK","Dr. Deepak","Miss\Mrs Gaire."])

Word_series3.str.contains("\. ")

如果你想匹配转义字符\本身,你要么必须使用四个反斜杠"\"要么以r"mystring"的形式将字符串编码为原始字符串,然后使用双反斜杠。原始字符串是 Python 中的一种替代字符串表示形式,它简化了在普通字符串上执行正则表达式的一些奇怪之处,如下面的代码片段所示:

# Match strings containing a backslash
Word_series3.str.contains(r"\\")

在处理正则表达式中的特殊字符串时,通常使用原始字符串,因为它避免了这些特殊字符可能出现的问题。

正则表达式通常用于匹配文本之间的电话号码、电子邮件地址和网址的模式。Pandas 有几个接受正则表达式模式并执行操作的字符串函数。我们现在熟悉这些功能:series.str.contains()series.str.replace()

现在,让我们在注释数据集中使用更多的函数。

使用series.str.count()统计每个字符串中模式的出现次数,如下所示:

text.str.count(r"[Ww]olves").head(8)

使用series.str.findall()获取每个匹配的子串,并将结果作为列表返回,如下所示:

text.str.findall(r"[Ww]olves").head(8)

操纵一根弦的方法有很多。我们选择了最基本的方法来说明,以便让您简单地理解。

进一步阅读

  • 用 NLTK 2.0 食谱处理 Python 文本雅各布·帕金斯,Packt 出版11 月 9 日2010
  • NLTK 精粹尼廷哈登亚·帕克特出版T52015 年 7 月 26 日
  • Python 自然语言处理实操Rajesh Arumugam,Rajalingappaa Shanmugamani,Packt Publishing7 月 17 日2018
  • 用 Python 进行数据分析David Taieb,Packt PublishingT512 月 31 日2018

第一部分:EDA 的基础

本节的主要目的是介绍探索性数据分析 ( EDA )的基础知识,并了解 EDA 过程的不同阶段。我们还将研究概要分析、质量评估的关键概念、EDA 的主要方面以及 EDA 中的挑战和机遇。除此之外,我们将发现不同的有用的可视化技术。最后,我们将讨论基本的数据转换技术,包括数据库风格的数据帧合并、转换技术以及数据转换的好处。

本节包含以下章节:

第二部分:描述性统计

描述性统计有助于总结所提供的数据集,并确定所考虑的数据的最重要特征。本节的主要目的是让您熟悉描述性统计及其主要技术,包括中心趋势的度量和可变性的度量。此外,我们将学习不同的数据集分组方法、相关性,更重要的是,时间序列分析。

本节包含以下章节:

第三部分:模型开发和评估

EDA 的主要目标之一是准备您的数据集,以开发能够表征感测数据的有用模型。要创建这样的模型,我们首先需要了解数据集。如果我们的数据集被标记,我们将执行有监督的学习任务,如果我们的数据没有被标记,那么我们将执行无监督的学习任务。此外,一旦我们创建了这些模型,我们就需要量化我们的模型有多有效。我们可以通过对这些模型进行几次评估来做到这一点。在本节中,我们将深入讨论如何使用 EDA 进行模型开发和评估。本节的主要目标是允许您在真实数据集上使用 EDA 技术,准备不同类型的模型,并对它们进行评估。

本节包含以下章节:

posted @ 2025-10-24 09:52  绝不原创的飞龙  阅读(4)  评论(0)    收藏  举报