Pandas-秘籍-一-

43 阅读1小时+

Pandas 秘籍(一)

原文:Pandas Cookbook

协议:CC BY-NC-SA 4.0

零、前言

自从 2012 年被《哈佛评论》(Harvard Review)称为,“21 世纪最性感的工作”之后,数据科学的知名度迅速上升。在 2016 年和 2017 年,它被 Glassdoor 排名第一。 行业的需求助推了数据科学的飞速普及。 几个应用在新闻中引起了轰动,例如 Netflix 提出了更好的电影推荐,IBM 沃森在 Jeopardy 击败了人类,特斯拉制造了无人驾驶汽车,美国职棒大联盟的球队发现了被低估的前景以及谷歌学习识别互联网上的猫。

几乎每个行业都在寻找使用数据科学来构建新技术或提供更深刻见解的方法。 由于取得了如此显着的成功,炒作的光环似乎封装了数据科学。 支持这种炒作的大多数科学进展都来自机器学习领域,机器学习领域产生了使预测负责人工智能的算法。

所有机器学习算法的基本组成部分当然是数据。 由于公司已经意识到这一点,所以不乏它。 商业智能公司 Domo 估计,在最近两年中已创建了 90% 的世界数据。 尽管机器学习得到了所有关注,但它完全依赖于所馈送数据的质量。 在数据到达机器学习算法的输入层之前,必须对其进行准备,并且为了正确准备数据,需要对它进行彻底的探索,以进行基本理解并识别不正确之处。 在探索数据之前,必须先将其捕获。

总之,我们可以将数据科学流水线分为三个阶段-数据捕获,数据探索和机器学习。 有大量工具可用于完成管道的每个阶段。 Pandas 是科学 Python 生态系统中用于数据探索和分析的主要工具。 它具有检查,清理,整理,过滤,转换,聚合甚至可视化(在一些帮助下)所有类型数据的强大能力。 它不是最初捕获数据的工具,也不是构建机器学习模型的工具。

对于许多使用 Python 的数据分析人员和科学家来说,绝大多数工作将使用 Pandas 来完成。 这可能是因为最初的数据探索和准备往往花费最多的时间。 一些整个项目仅包含数据探索,没有机器学习组件。 数据科学家在此阶段花费了太多时间,以至于出现了永恒的传说 - 数据科学家花费了 80% 的时间来清理数据,另外 20% 的时间抱怨清理数据。

尽管有大量的开放源代码和免费编程语言可用于数据探索,但目前该领域仅由两个参与者(Python 和 R)主导。这两种语言的语法差异很大,但都具有进行数据分析和执行机器学习的能力。 受欢迎程度的一种度量是在受欢迎的 Q&A 站点 Stack Overflow 上提出的问题数量:

尽管这不是使用情况的真实衡量标准,但很显然,Python 和 R 都已变得越来越流行,这可能是由于它们的数据科学功能所致。 有趣的是,直到 2012 年数据科学开始兴起,Python 问题的比例一直保持不变。 这张图最令人惊讶的是,Pandas 问题现在占 Stack Overflow 上所有最新问题的百分之一。

Python 之所以成为数据科学首选语言的原因之一是,它是一种相当容易学习和开发的语言,因此入门门槛低。 它也是免费和开源的,能够在各种硬件和软件上运行,并且轻而易举地启动并运行。 它有一个非常活跃的社区,在线上有大量免费资源。 在我看来,Python 是用于开发程序的最有趣的语言之一。 语法非常清晰,简洁和直观,但像所有语言一样,需要花费相当长的时间才能掌握。

由于 Python 并非像 R 那样用于数据分析,因此语法可能不像其他 Python 库那样自然。 实际上,这可能是其中有太多栈溢出问题的原因的一部分。 尽管 Pandas 功能强大,但通常编写得很差。 本书的主要目的之一是展示高性能和惯用的 Pandas 代码。

不幸的是,Stack Overflow 发挥了其巨大的作用,使错误信息永久存在,并且是大量写得不好的 Pandas 的来源。 这实际上不是 Stack Overflow 或其社区的错。 Pandas 是一个开放源代码项目,即使在最近,它也进行了许多重大更改,因为它在 2018 年已接近成立十年。然而,开放源代码的优点在于,新功能一直在不断增加。

本书中的秘籍是根据我作为数据科学家工作,建立和托管数周的数据探索训练营,回答关于 Stack Overflow 的数百个问题以及为我的本地聚会小组创建教程而制定的。 这些秘籍不仅为常见数据问题提供惯用的解决方案,而且还带您穿越许多真实世界的数据集,在这些数据集中经常发现令人惊讶的见解。 这些秘籍还将帮助您掌握 Pandas 库,从而极大地提高生产力。 仅对 Pandas 有粗略了解的人与对 Pandas 有所了解的人之间存在巨大差异。 有很多有趣而有趣的技巧来解决您的数据问题,这些技巧只有在您真正了解库内外的情况下才会显现出来。 就个人而言,我发现 Pandas 是一种用于分析数据的令人愉悦且有趣的工具,希望您和我一起享受旅途。 如有疑问,请随时在 Twitter 上与我联系:@TedPetrou

本书涵盖的内容

第 1 章,“Pandas 基础”涵盖了用于识别两个主要 Pandas 数据结构(序列和数据帧)的组成部分的解剖结构和词汇表。 每一列必须仅具有一种数据类型,并且涵盖了每种数据类型。 您将学习如何通过调用和链接它们的方法来释放序列和数据帧的潜能。

第 2 章,“基本数据帧操作”着重介绍您将在数据分析期间执行的最关键和最常见的操作。

第 3 章,“开始数据分析”可帮助您开发例程,以在读取数据后开始使用。 其他有趣的发现将被发现。

第 4 章,“选择数据子集”涵盖了选择不同数据子集的许多不同且可能引起混淆的方式。

第 5 章,“布尔索引”涵盖了查询数据以根据布尔条件选择子集的过程。

第 6 章,“索引对齐”以非常重要且经常被误解的index对象为目标。 错误使用索引会导致许多错误的结果,这些秘籍向您展示了如何正确使用它来提供有力的结果。

第 7 章,“进行聚集,过滤和转换的分组”涵盖了强大的分组函数,这些函数几乎是数据分析期间始终需要的。 您将构建自定义函数以应用于您的组。

第 8 章,“将数据重组为整齐的表格”,解释了整洁的数据及其重要性,然后向您展示了如何将许多不同形式的杂乱数据集转换为整洁的数据集。

第 9 章,“组合 Pandas 对象”涵盖了许多可用于垂直或水平组合数据帧和序列的方法。 我们还将进行一些网上爬虫比较,以比较特朗普总统和奥巴马总统的支持率,并连接到 SQL 关系数据库。

第 10 章,“时间序列分析”涵盖了高级且强大的时间序列函数,可以按任何可能的时间维度进行剖析。

第 11 章,“使用 Matplotlib,Pandas 和 Seaborn 进行可视化”介绍了 matplotlib 库,该库负责 Pandas 中的所有绘图。 然后,我们将重点转移到 Pandas plot方法上,最后转移到seaborn库,该库能够产生在美学上令人愉悦的可视化效果,而这些效果在 Pandas 中不直接可用。

这本书需要什么

Pandas 是用于 Python 编程语言的第三方包,在本书出版时,它的版本为 0.20。 目前,Python 有两个主要受支持的版本,版本 2.7 和 3.6。 Python 3 是未来,现在强烈建议所有 Python 的科学计算用户都使用它,因为 2020 年将不再支持 Python2。本书中的所有示例均已在 Python 3.6 上以 pandas 0.20 运行和测试。

除了 Pandas,您还需要安装 matplotlib 2.0 版和 seaborn 0.8 版可视化库。 对 Pandas 的主要依赖是 NumPy 库,它构成了大多数流行的 Python 科学计算库的基础。

您可以通过多种方式来安装 Pandas 和计算机上提到的其余库,但是到目前为止,最简单的方法是安装 Anaconda 发行版。 它由 Continuum Analytics 创建,将所有流行的用于科学计算的库打包到一个可下载的文件中,该文件可在 Windows,Mac OSX 和 Linux 上使用。 访问下载页面以获取 Anaconda 发行版。

除了所有科学计算库之外,Anaconda 发行版还附带 Jupyter 笔记本,这是一个基于浏览器的程序,可使用 Python 和许多其他语言进行开发。 本书的所有秘籍都是在 Jupyter 笔记本内部开发的,每一章的所有单个笔记本都可以使用。

无需使用 Anaconda 发行版就可以安装本书所需的所有库。 对于感兴趣的用户,请访问 Pandas 安装页面

运行 Jupyter 笔记本

建议阅读本书的全部内容的方法是启动并运行 Jupyter 笔记本,以便您可以在阅读秘籍的同时运行代码。 与仅阅读本书相比,这使您可以自己进行探索并获得更深刻的理解。

假设您已经在计算机上安装了 Anaconda 发行版,则可以使用两个选项来启动 Jupyter 笔记本:

  • 使用程序 Anaconda Navigator
  • 从终端/命令提示符运行jupyter notebook命令

Anaconda Navigator 是基于 GUI 的工具,可让您轻松查找 Anaconda 提供的所有不同软件。 运行程序将为您提供如下屏幕:

如您所见,有许多可用的程序。 单击“启动”以打开 Jupyter 笔记本。 浏览器中将打开一个新标签,向您显示主目录中的文件夹和文件列表:

您可以通过打开终端/命令提示符并运行Jupyter 笔记本,命令来启动 Jupyter 笔记本,而不是使用 Anaconda Navigator,如下所示:

不必从主目录运行此命令。 您可以从任何位置运行它,浏览器中的内容将反映该位置。

尽管我们现在已经启动了 Jupyter 笔记本程序,但实际上并没有启动一个单独的笔记本就可以开始用 Python 开发。 为此,您可以单击页面右侧的“新建”按钮,该按钮将下拉列出所有可能使用的内核的列表。 如果您刚刚下载了 Anaconda,则只有一个可用的内核(Python 3)。 选择 Python 3 内核后,将在浏览器中打开一个新标签,您可以在其中开始编写 Python 代码:

当然,您可以打开以前创建的笔记本,而不用开始新的笔记本。 为此,只需在 Jupyter 笔记本浏览器主页中提供的文件系统中导航,然后选择要打开的笔记本即可。 所有 Jupyter 笔记本文件都以.ipynb结尾。 例如,当您导航到这本书的笔记本文件的位置时,您将看到所有这些文件,如下所示:

这本书是给谁的

本书包含近 100 种秘籍,从非常简单到高级。 所有秘籍都力求以清晰,简洁,现代的惯用 Pandas 代码编写。 “工作原理”部分包含对秘籍每个步骤的复杂性的非常详细的描述。 通常,在“更多”部分,您将获得似乎是一个全新的秘籍。 这本书密密麻麻地包装着大量的 Pandas 代码。

概括地说,与后五章相比,前六章中的秘籍更简单,更侧重于 Pandas 的基本和基本操作,后五章中的重点是更高级的操作并且更受项目驱动。 由于复杂性范围很广,因此这本书对于新手和日常用户都非常有用。 根据我的经验,即使定期使用 Pandas 的人也不会在没有惯用的 Pandas 密码的情况下掌握它。 Pandas 提供的宽度在一定程度上促进了这一点。 几乎总是有多种方法可以完成相同的操作,这些方法可以使用户以非常低效的方式获得所需的结果。 在同一问题的两组 Pandas 解决方案之间看到一个数量级或更多个性能差异并不少见。

本书唯一真正的先决条件是 Python 的基础知识。 假定读者熟悉 Python 中所有常见的内置数据容器,例如列表,集合,字典和元组。

如何充分利用这本书

您可以采取几项措施来充分利用本书。 首先,也是最重要的是,您应该下载所有代码,这些代码将存储在 Jupyter 笔记本中。 阅读每个秘籍时,请在笔记本中运行代码的每个步骤。 在运行代码时,请确保自己进行探索。 其次,在您的浏览器选项卡之一中打开 Pandas 官方文档。 Pandas 文档是一个很好的资源,其中包含超过 1000 页的材料。 文档中有大多数 Pandas 操作的示例,它们通常会直接链接到“另见”部分。 虽然涵盖了大多数操作的基础知识,但它提供了一些琐碎的示例和伪造的数据,这些伪造的数据并不能反映您在分析现实世界中的数据集时可能遇到的情况。

约定

在本书中,您将找到一些可以区分不同类型信息的文本样式。 最常见的是,您会在每个秘籍中看到如下所示的代码块:

>>> employee = pd.read_csv('data/employee')
>>> max_dept_salary = employee.groupby('DEPARTMENT')['BASE_SALARY'].max()

在笔记本中输出时,pandas 序列和数据帧的样式不同。 Pandas 序列没有特殊的格式,只是原始文本。 它们将直接出现在代码块本身中创建它们的代码行的前面,如下所示:

>>> max_dept_salary.head()
DEPARTMENT
Admn. & Regulatory Affairs      140416.0
City Controller's Office         64251.0
City Council                    100000.0
Convention and Entertainment     38397.0
Dept of Neighborhoods (DON)      89221.0
Name: BASE_SALARY, dtype: float64

另一方面,数据帧在笔记本中具有良好的风格,并显示为代码框外部的图像,如下所示:

>>> employee.pivot_table(index='DEPARTMENT', 
                         columns='GENDER', 
                         values='BASE_SALARY').round(0).head()

文本,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄中的代码字如下所示:为了通过GENDER查找平均BASE_SALARY,可以使用pivot_table方法。

新术语重要词以粗体显示。 您在屏幕上看到的字词,例如在菜单或对话框中,将像这样显示在文本中:在 Jupyter 笔记本电脑中,按住快捷键S + Tab + Tab,将光标置于对象中的某个位置,将弹出文档字符串的窗口,使得该方法更易于使用。

提示和技巧是这样的。警告或重要提示出现在这样的框中。

每个秘籍的假设

应该假设在每个秘籍的开始,都会将 pandas,NumPy 和 matplotlib 导入命名空间。 为了将绘图直接嵌入到笔记本中,还必须运行魔术命令%matplotlib inline。 同样,所有数据都存储在 data目录中,并且最通常存储为 CSV 文件,可以通过read_csv函数直接读取。

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

>>> my_dataframe = pd.read_csv('data/dataset_name.csv')

数据集说明

本书共使用了大约十二个数据集。 完成秘籍中的步骤时,在每个数据集上具有背景信息可能会非常有帮助。 可以在 PacktPublishing/Pandas-Cookbookdataset_descriptions Jupyter 笔记本中找到每个数据集的详细说明。 对于每个数据存储,将有一个列列表,有关每个列的信息以及有关如何获取数据的注释。

标题

在本书中,您会发现经常出现的几个标题(准备,操作步骤,工作原理,更多以及另见)。

为了给出有关如何完成秘籍的明确说明,我们使用以下部分:

准备

本节将告诉您秘籍中的预期内容,并介绍如何设置秘籍所需的任何软件或任何初步设置。

操作步骤

本节包含遵循秘籍所需的步骤。

工作原理

本节通常包括对上一节中发生的情况的详细说明。

更多

本节包含有关秘籍的其他信息,以使读者对秘籍有更多的了解。

另见

本部分提供了指向该秘籍其他有用信息的有用链接。

一、Pandas 基础

在本章中,我们将介绍以下内容:

  • 剖析数据帧的结构
  • 访问主要的数据帧组件
  • 了解数据类型
  • 选择单列数据作为序列
  • 调用序列方法
  • 与运算符一起使用序列
  • 将序列方法链接在一起
  • 使索引有意义
  • 重命名行和列名称
  • 创建和删除列

介绍

本章的目的是通过彻底检查序列和数据帧数据结构来介绍 Pandas 的基础。 对于 Pandas 用户来说,了解序列和数据帧的每个组件,并了解 Pandas 中的每一列数据正好具有一种数据类型,这一点至关重要。

在本章中,您将学习如何从数据帧中选择一个数据列,该数据列将作为序列返回。 使用此一维对象可以轻松显示不同的方法和运算符如何工作。 许多序列方法返回另一个序列作为输出。 这导致有可能连续调用其他方法,这被称为方法链接

序列和数据帧的索引组件是将 Pandas 与其他大多数数据分析库区分开的组件,并且是了解执行多少操作的关键。 当我们将其用作序列值的有意义的标签时,我们将瞥见这个强大的对象。 最后两个秘籍包含在数据分析期间经常发生的简单任务。

剖析数据帧的结构

在深入研究 Pandas 之前,值得了解数据帧的组件。 在视觉上,Pandas 数据帧的输出显示(在 Jupyter 笔记本中)似乎只不过是由行和列组成的普通数据表。 隐藏在表面下方的是三个组成部分-您必须具备的索引数据(也称为)。 请注意,以便最大化数据帧的全部潜力。

准备

此秘籍将电影数据集读入 pandas 数据帧中,并提供其所有主要成分的标签图。

操作步骤

  1. 使用read_csv函数读取影片数据集,并使用head方法显示前五行:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie.head()
  1. 分析数据帧的标记解剖结构:

工作原理

Pandas 首先使用出色且通用的read_csv函数将数据从磁盘读入内存,然后读入数据帧。 列和索引的输出均以粗体显示,这使它们易于识别。 按照惯例,术语索引标签列名分别是指索引和列的各个成员。 术语索引整体上指所有索引标签,正如术语整体上指所有列名称一样。

列和索引用于特定目的,即为数据帧的列和行提供标签。 这些标签允许直接轻松地访问不同的数据子集。 当多个序列或数据帧组合在一起时,索引将在进行任何计算之前首先对齐。 列和索引统称为

DataFrame具有两个轴:垂直轴(索引)和水平轴(列)。 Pandas 借鉴了 NumPy 的约定,并使用整数 0/1 作为引用垂直/水平轴的另一种方式。

数据帧的数据(值)始终为常规字体,并且是与列或索引完全独立的组件。 Pandas 使用NaN不是数字)来表示缺失值。 请注意,即使color列仅包含字符串值,它仍使用NaN表示缺少的值。

列中间的三个连续点表示存在至少一列,但由于列数超过了预定义的显示限制,因此未显示。

Python 标准库包含csv模块,可用于解析和读取数据。 Pandas 的read_csv函数比该模块提供了性能和功能上的强大提升。

更多

head方法接受单个参数n,该参数控制显示的行数。 同样,tail方法返回最后的n行。

另见

访问主要的数据帧组件

可以直接从数据帧访问三个数据帧组件(索引,列和数据)中的每一个。 每个组件本身都是一个 Python 对象,具有自己的独特属性和方法。 通常,您希望对单个组件而不是对整个数据帧进行操作。

准备

此秘籍将数据帧的索引,列和数据提取到单独的变量中,然后说明如何从同一对象继承列和索引。

操作步骤

  1. 使用数据帧属性indexcolumnsvalues将索引,列和数据分配给它们自己的变量:
>>> movie = pd.read_csv('data/movie.csv')
>>> index = movie.index
>>> columns = movie.columns
>>> data = movie.values
  1. 显示每个组件的值:
>>> index
RangeIndex(start=0, stop=5043, step=1)

>>> columns
Index(['color', 'director_name', 'num_critic_for_reviews',
       ...
       'imdb_score', 'aspect_ratio', 'movie_facebook_likes'],
       dtype='object')

>>> data
array([['Color', 'James Cameron', 723.0, ..., 7.9, 1.78, 33000],
       ..., 
       ['Color', 'Jon Gunn', 43.0, ..., 6.6, 1.85, 456]],
       dtype=object)
  1. 输出每个数据帧组件的类型。 类型的名称是输出最后一个点后面的单词:
>>> type(index)
pandas.core.indexes.range.RangeIndex

>>> type(columns)
pandas.core.indexes.base.Index

>>> type(data)
numpy.ndarray
  1. 有趣的是,索引和列的类型似乎都密切相关。 内置的issubclass方法检查RangeIndex是否确实是Index的子类:
>>> issubclass(pd.RangeIndex, pd.Index)
True

工作原理

您可以使用indexcolumnsvalues属性访问数据帧的三个主要组件。columns属性的输出似乎只是列名称的序列。 从技术上讲,此列名称序列是Index对象。 函数type的输出是对象的完全限定的类名

变量columns的对象的全限定类名称为pandas.core.indexes.base.Index。 它以包名称开头,后跟模块路径,并以类型名称结尾。 引用对象的常用方法是在包名称后加上对象类型的名称。 在这种情况下,我们将这些列称为 Pandas 的Index对象。

内置的subclass函数检查第一个参数是否从第二个参数继承。IndexRangeIndex对象非常相似,实际上,pandas 具有许多专门为索引或列保留的相似对象。 索引和列都必须都是某种Index对象。 本质上,索引和列表示同一事物,但沿不同的轴。 有时将它们称为行索引列索引

在这种情况下,Index对象是指可用于索引或列的所有可能的对象。 它们都是pd.Index的子类。 这是Index对象的完整列表:CategoricalIndexMultiIndexIntervalIndexInt64IndexUInt64IndexFloat64IndexRangeIndexTimedeltaIndexDatetimeIndexPeriodIndex

RangeIndexIndex对象的一种特殊类型,类似于 Python 的range对象。 直到必须将其整个值序列加载到内存中为止,从而节省了内存。 它完全由其开始,停止和步长值定义。

更多

尽可能使用哈希表实现Index对象,以实现非常快速的选择和数据对齐。 它们与 Python 集相似,因为它们支持诸如相交和并集之类的操作,但是由于它们的排序允许重复,因此它们是不同的。

Python 字典和集合也通过哈希表实现,无论对象的大小如何,都可以在恒定时间内非常快速地进行成员资格检查。

注意values数据帧属性如何返回 NumPy N 维数组或ndarray。 大部分 Pandas 都严重依赖ndarray。 在索引,列和数据之下是 NumPy ndarrays。 可以将它们视为构建许多其他对象的 Pandas 的基本对象。 要看到这一点,我们可以查看indexcolumns的值:

>>> index.values
array([   0,    1,    2, ..., 4913, 4914, 4915])

>>> columns.values
array(['color', 'director_name', 'num_critic_for_reviews',
 ...
 'imdb_score', 'aspect_ratio', 'movie_facebook_likes'],
 dtype=object)

另见

了解数据类型

用非常广泛的术语来说,数据可以分类为连续的或分类的。 连续数据始终是数字,代表某种度量,例如身高,工资或薪水。 连续数据可能具有无限数量的可能性。 另一方面,分类数据代表离散的有限数量的值,例如汽车颜色,扑克手类型或谷类食品品牌。

Pandas 没有将数据大致分为连续数据或分类数据。 相反,它对许多不同的数据类型都有精确的技术定义。 下表包含所有 pandas 数据类型,及其等效字符串,以及每种类型的一些注释:

通用数据类型名称NumPy / Pandas 对象Pandas 字符串名称注释
布尔np.boolbool存储为单个字节。
整数np.intint默认为 64 位。 也可以使用无符号整数 - np.uint
浮点np.floatfloat默认为 64 位。
复数np.complexcomplex在数据分析中很少见。
对象np.objectOobject通常为字符串,但是对于具有多种不同类型的列或其他 Python 对象(元组,列表,字典等)来说是万能的。
日期时间np.datetime64, pd.Timestampdatetime64具有纳秒精度的特定时间点。
时间增量np.timedelta64, pd.Timedeltatimedelta64时间增量,从几天到纳秒。
类别pd.CategoricalCategorical仅限于 Pandas。 对于唯一值相对较少的对象列很有用。

准备

在此秘籍中,我们将显示数据帧中每一列的数据类型。 了解每一列中保存的数据类型至关重要,因为它会从根本上改变可能进行的操作的类型。

操作步骤

  1. 使用dtypes属性显示每一列及其数据类型:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie.dtypes
color                       object
director_name               object
num_critic_for_reviews     float64
duration                   float64
director_facebook_likes    float64
                            ...   
title_year                 float64
actor_2_facebook_likes     float64
imdb_score                 float64
aspect_ratio               float64
movie_facebook_likes         int64
Length: 28, dtype: object
  1. 使用get_dtype_counts方法返回每种数据类型的计数:
>>> movie.get_dtype_counts()
float64    13
int64       3
object     12

工作原理

每个数据帧的列必须恰好是一种类型。 例如,aspect_ratio列中的每个值都是 64 位浮点数,movie_facebook_likes列中的每个值都是 64 位整数。 Pandas 默认使用其核心数字类型,整数,并且浮点数为 64 位,而不管所有数据放入内存所需的大小如何。 即使列完全由整数值 0 组成,数据类型仍将为int64get_dtype_counts是一种方便的方法,用于直接返回数据帧中所有数据类型的计数。

同构数据是指所有具有相同类型的列的另一个术语。 整个数据帧可能包含不同列的不同数据类型的异构数据

对象数据类型是一种与其他数据类型不同的数据类型。 对象数据类型的列可以包含任何有效 Python 对象的值。 通常,当列属于对象数据类型时,它表示整个列都是字符串。 不一定是这种情况,因为这些列可能包含整数,布尔值,字符串或其他甚至更复杂的 Python 对象(例如列表或字典)的混合物。 对象数据类型是 Pandas 无法识别为其他任何特定类型的列的全部内容。

更多

几乎所有的 Pandas 数据类型都是直接从 NumPy 构建的。 这种紧密的集成使用户可以更轻松地集成 Pandas 和 NumPy 操作。 随着 Pandas 越来越大,越来越流行,事实证明,对象数据类型对于具有字符串值的所有列来说太通用了。 Pandas 创建了自己的分类数据类型,以处理具有固定数量的可能值的字符串(或数字)列。

另见

选择单列数据作为序列

序列是来自数据帧的单列数据。 它是数据的一个维度,仅由索引和数据组成。

准备

此秘籍检查了两种不同的语法以选择“序列”,一种使用索引运算符,另一种使用点符号。

操作步骤

  1. 将列名作为字符串传递给索引运算符以选择数据的序列:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie['director_name']
  1. 或者,您可以使用点符号来完成相同的任务:
>>> movie.director_name
  1. 检查序列解剖结构:
  2. 验证输出是否为序列:
>>> type(movie['director_name'])
pandas.core.series.Series

工作原理

Python 有几个内置对象用于包含数据,例如列表,元组和字典。 所有这三个对象都使用索引运算符来选择其数据。数据帧是更强大,更复杂的数据容器,但它们也使用索引运算符作为选择数据的主要方式。 将单个字符串传递给数据帧索引运算符将返回一个序列。

序列的视觉输出风格比数据帧少。 它代表一列数据。 连同索引和值一起,输出显示序列的名称,长度和数据类型。

或者,虽然不建议这样做,但可能会出错,但是可以使用带有列名作为属性的点表示法来访问数据列。 尽管它适用于此特定示例,但这不是最佳实践,并且容易出错和误用。 不能以这种方式访问​​带有空格或特殊字符的列名称。 如果列名称为director name,则该操作将失败。 与数据帧方法冲突的列名,例如count,也无法使用点符号正确选择。 分配新值或删除带有点符号的列可能会导致意外的结果。 因此,在生产代码中应避免使用点表示法访问列。

更多

如果会引起麻烦,为什么有人会使用点符号语法呢? 程序员很懒,而且键入的字符更少。 但主要是,当您想使用自动完成智能功能时,它非常方便。 因此,在本书中有时会使用点标记进行列选择。 自动完成智能非常适合帮助您了解对象可用的所有可能的属性和方法。

在使用步骤 1 中的索引运算符后,尝试链接操作时,智能将无法工作,但将继续使用步骤 2 中的点符号。下面的屏幕快照显示了在选择了索引之后的弹出窗口。director_name带点符号。 在点后按选项卡后,所有可能的属性和方法将显示在列表中:

在 Jupyter 笔记本中,在按下Shift + Tab + Tab,并将光标放在对象中某处的情况下,将弹出文档字符串窗口,使该方法更易于使用。 如果您在使用索引运算符选择一列后尝试链接一个操作,则该智能再次消失。

注意点表示法的另一个原因是,它在流行的问答网站 Stack Overflow 上在线使用的数量激增。 另外,请注意,旧列名称现在是序列的name,实际上已经成为一个属性:

>>> director = movie['director_name']
>>> director.name
'director_name'

可以使用to_frame方法将此序列转换为单列数据帧。 此方法将使用序列名称作为新的列名称:

>>> director.to_frame()

另见

  • 要了解 Python 对象如何获得使用索引运算符的能力,请参见 Python 文档中的__getitem__特殊方法
  • 请参阅第 2 章,“基本数据帧操作”的“选择多个数据帧的列”秘籍

调用序列方法

利用一维序列是所有 Pandas 数据分析的组成部分。 典型的工作流程将使您在序列和数据帧上的执行语句之间来回切换。 调用序列方法是使用序列提供的功能的主要方法。

准备

序列和数据帧都具有强大的函数。 我们可以使用dir函数来揭示序列的所有属性和方法。 此外,我们可以找到序列和数据帧共有的属性和方法的数量。 这两个对象共享绝大多数的属性和方法名称:

>>> s_attr_methods = set(dir(pd.Series))
>>> len(s_attr_methods)
442

>>> df_attr_methods = set(dir(pd.DataFrame))
>>> len(df_attr_methods)
445

>>> len(s_attr_methods & df_attr_methods)
376

本秘籍涵盖了最常见且功能最强大的序列方法。 对于数据帧,许多方法几乎是等效的。

操作步骤

  1. 读完电影数据集后,让我们选择两个具有不同数据类型的序列。director_name列包含字符串,形式上是对象数据类型,列actor_1_facebook_likes包含数字数据,形式上是float64
>>> movie = pd.read_csv('data/movie.csv')
>>> director = movie['director_name']
>>> actor_1_fb_likes = movie['actor_1_facebook_likes']
  1. 检查每个序列的head
>>> director.head()
0        James Cameron
1       Gore Verbinski
2           Sam Mendes
3    Christopher Nolan
4          Doug Walker
Name: director_name, dtype: object

>>> actor_1_fb_likes.head()
0     1000.0
1    40000.0
2    11000.0
3    27000.0
4      131.0
Name: actor_1_facebook_likes, dtype: float64
  1. 序列的数据类型通常确定哪种方法最有用。 例如,对象数据类型序列最有用的方法之一是value_counts,它计算每个唯一值的所有出现次数:
>>> director.value_counts()
Steven Spielberg        26
Woody Allen             22
Martin Scorsese         20
Clint Eastwood          20
                        ..
Fatih Akin               1
Analeine Cal y Mayor     1
Andrew Douglas           1
Scott Speer              1
Name: director_name, Length: 2397, dtype: int64
  1. value_counts方法通常对于具有对象数据类型的序列更为有用,但有时也可以提供对数值序列的深入了解。 与actor_1_fb_likes一起使用时,似乎已将较高的数字四舍五入到最接近的千位,因为不太可能有那么多电影获得准确的 1,000 个赞:
>>> actor_1_fb_likes.value_counts()
1000.0     436
11000.0    206
2000.0     189
3000.0     150
          ... 
216.0        1
859.0        1
225.0        1
334.0        1
Name: actor_1_facebook_likes, Length: 877, dtype: int64
  1. 可以使用sizeshape参数或len函数对序列中的元素数进行计数:
>>> director.size
4916
>>> director.shape
(4916,)
>>> len(director)
4916
  1. 此外,还有一种有用但令人困惑的count方法,它返回非缺失值的数量:
>>> director.count()
4814
>>> actor_1_fb_likes.count()
4909
  1. 基本摘要统计信息可以通过minmaxmeanmedianstdsum方法得出:
>>> actor_1_fb_likes.min(), actor_1_fb_likes.max(), \
    actor_1_fb_likes.mean(), actor_1_fb_likes.median(), \
    actor_1_fb_likes.std(), actor_1_fb_likes.sum()
(0.0, 640000.0, 6494.488490527602, 982.0, 15106.98, 31881444.0)
  1. 为了简化步骤 7,您可以使用describe方法一次返回汇总统计信息和一些分位数。 当describe与对象数据类型列一起使用时,将返回完全不同的输出:
>>> actor_1_fb_likes.describe()
count      4909.000000
mean       6494.488491
std       15106.986884
min           0.000000
25%         607.000000
50%         982.000000
75%       11000.000000
max      640000.000000
Name: actor_1_facebook_likes, dtype: float64

>>> director.describe()
count                 4814
unique                2397
top       Steven Spielberg
freq                    26
Name: director_name, dtype: object
  1. quantile方法用于计算数字数据的精确分位数:
>>> actor_1_fb_likes.quantile(.2)
510

>>> actor_1_fb_likes.quantile([.1, .2, .3, .4, .5,
                               .6, .7, .8, .9])
0.1      240.0
0.2      510.0
0.3      694.0
0.4      854.0
        ...   
0.6     1000.0
0.7     8000.0
0.8    13000.0
0.9    18000.0
Name: actor_1_facebook_likes, Length: 9, dtype: float64
  1. 由于第 6 步中的count方法返回的值小于在第 5 步中找到的序列元素的总数,因此我们知道每个序列中都有缺失的值。isnull方法可用于确定每个单独的值是否丢失。 结果将是布尔序列,其长度与原始序列相同:
>>> director.isnull()
0       False
1       False
2       False
3       False
        ...  
4912     True
4913    False
4914    False
4915    False
Name: director_name, Length: 4916, dtype: bool
  1. 可以用fillna方法替换序列中的所有缺失值:
>>> actor_1_fb_likes_filled = actor_1_fb_likes.fillna(0)
>>> actor_1_fb_likes_filled.count()
4916
  1. 要删除缺少值的序列元素,请使用dropna
>>> actor_1_fb_likes_dropped = actor_1_fb_likes.dropna()
>>> actor_1_fb_likes_dropped.size
4909

工作原理

将字符串传递给数据帧的索引运算符会将单个列选择为序列。 选择本秘籍中使用的方法是因为它们在数据分析中的使用频率。

本秘籍中的步骤应简单明了,并具有易于解释的输出。 即使输出易于阅读,您也可能无法跟踪返回的对象。 它是标量值,元组,另一个序列还是其他 Python 对象? 花一点时间,看看每一步之后返回的输出。 您可以命名返回的对象吗?

步骤 1 中head方法的结果是另一个序列。value_counts方法也产生一个序列,但具有原始序列的唯一值作为索引,计数作为其值。 在步骤 5 中,sizecount返回标量值,但是shape返回单项元组。

形状属性返回一个单项元组似乎很奇怪,但这是从 NumPy 借来的约定,它允许任意数量的维度的数组。

在步骤 7 中,每个方法返回一个标量值,并作为元组输出。 这是因为 Python 将仅包含逗号分隔值且不带括号的表达式视为元组。

在步骤 8 中,describe返回一个序列,其所有摘要统计信息名称均作为索引,而实际统计信息则为值。

在步骤 9 中,quantile是灵活的,当传递单个值时返回标量值,但在给定列表时返回序列。

从步骤 10、11 和 12,isnullfillnadropna都返回一个序列。

更多

value_counts方法是最有用的序列方法之一,在探索性分析中特别是在分类列分析中被大量使用。 它默认返回计数,但是通过将normalize参数设置为True,则返回相对频率,这提供了另一种分布图:

>>> director.value_counts(normalize=True)
Steven Spielberg        0.005401
Woody Allen             0.004570
Martin Scorsese         0.004155
Clint Eastwood          0.004155
                          ...   
Fatih Akin              0.000208
Analeine Cal y Mayor    0.000208
Andrew Douglas          0.000208
Scott Speer             0.000208
Name: director_name, Length: 2397, dtype: float64

在此秘籍中,我们通过观察count方法的结果与size属性不匹配,确定该序列中缺少值。 一种更直接的方法是使用hasnans属性:

>>> director.hasnans
True

isnull存在一个补充:notnull方法,该方法为所有非缺失值返回True

>>> director.notnull()
0        True
1        True
2        True
3        True
        ...  
4912    False
4913     True
4914     True
4915     True
Name: director_name, Length: 4916, dtype: bool

另见

  • 要连续调用许多序列方法,请在本章中一起参考“链接序列方法”秘籍

与运算符一起使用序列

Python 中存在大量用于操作对象的运算符。 运算符本身不是对象,而是强制对对象执行操作的语法结构和关键字。 例如,将加法运算符放在两个整数之间时,Python 会将它们加在一起。 在以下代码中查看更多运算符示例:

>>> 5 + 9   # plus operator example adds 5 and 9
14

>>> 4 ** 2  # exponentiation operator raises 4 to the second power
16

>>> a = 10  # assignment operator assigns 10 to a

>>> 5 <= 9  # less than or equal to operator returns a boolean
True

运算符可以处理任何类型的对象,而不仅仅是数字数据。 这些示例显示了正在操作的不同对象:

>>> 'abcde' + 'fg' 
'abcdefg'

>>> not (5 <= 9)
False

>>> 7 in [1, 2, 6]
False

>>> set([1,2,3]) & set([2,3,4])
set([2,3])

访问 TutorialsPoint,以查看所有基本 Python 运算符的表。 并非对每个对象都实现所有运算符。 这些示例在使用运算符时都会产生错误:

>>> [1, 2, 3] - 3
TypeError: unsupported operand type(s) for -: 'list' and 'int'

>>> a = set([1,2,3])     
>>> a[0] TypeError: 'set' object does not support indexing

序列和数据帧对象可与大多数 Python 运算符一起使用。

准备

在此秘籍中,各种运算符将应用于不同的序列对象,以产生具有完全不同值的新序列。

操作步骤

  1. 选择imdb_score列作为序列:
>>> movie = pd.read_csv('data/movie.csv')
>>> imdb_score = movie['imdb_score']
>>> imdb_score
0       7.9
1       7.1
2       6.8
       ... 
4913    6.3
4914    6.3
4915    6.6
Name: imdb_score, Length: 4916, dtype: float64
  1. 使用加法运算符可向每个序列元素添加一个:
>>> imdb_score + 1
0       8.9
1       8.1
2       7.8
       ... 
4913    7.3
4914    7.3
4915    7.6
Name: imdb_score, Length: 4916, dtype: float64
  1. 其他基本算术运算符减号(-),乘法(*),除法(/)和幂(**)与标量值相似。 在这一步中,我们将序列乘以2.5
>>> imdb_score * 2.5
0       19.75
1       17.75
2       17.00
        ...  
4913    15.75
4914    15.75
4915    16.50
Name: imdb_score, Length: 4916, dtype: float64
  1. Python 使用两个连续的除法运算符(//)进行地板除法,并使用百分号(%)进行模数运算,这将在除法后返回余数。 序列使用这些相同的方式:
>>> imdb_score // 7
0       1.0
1       1.0
2       0.0
       ... 
4913    0.0
4914    0.0
4915    0.0
Name: imdb_score, Length: 4916, dtype: float64
  1. 存在六个比较运算符,它们大于(>),小于(<),大于或等于(>=,小于或等于(<=),等于(==),并且不等于(!=)。 每个比较运算符都会根据条件的结果将序列中的每个值转换为TrueFalse
>>> imdb_score > 7
0        True
1        True
2       False
        ...  
4913    False
4914    False
4915    False
Name: imdb_score, Length: 4916, dtype: bool

>>> director = movie['director_name']
>>> director == 'James Cameron'
0        True
1       False
2       False
        ...  
4913    False
4914    False
4915    False
Name: director_name, Length: 4916, dtype: bool

工作原理

本秘籍中使用的所有运算符都将相同的操作应用于序列中的每个元素。 在本机 Python 中,这将需要一个for循环在应用操作之前遍历序列中的每个项目。 Pandas 严重依赖 NumPy 库,该库允许进行向量化计算,也可以对整个数据序列进行操作而无需显式编写for循环。 每个操作都返回一个具有相同索引的序列,但其值已被运算符修改。

更多

此秘籍中使用的所有运算符都具有等效的方法,这些方法可产生完全相同的结果。 例如,在步骤 1 中,可以用add方法再现imdb_score + 1。 检查以下代码以查看秘籍中每个步骤的方法版本:

>>> imdb_score.add(1)              # imdb_score + 1
>>> imdb_score.mul(2.5)            # imdb_score * 2.5
>>> imdb_score.floordiv(7)         # imdb_score // 7
>>> imdb_score.gt(7)               # imdb_score > 7
>>> director.eq('James Cameron')   # director == 'James Cameron'

为什么 Pandas 提供与这些运算符等效的方法? 从本质上讲,运算符只能以一种方式进行操作。 另一方面,方法可以具有允许您更改其默认行为的参数:

运算符分组运算符序列方法名称
算术+-*///%**addsubmuldivfloordivmodpow
比较<><=>===!=ltgtlegeeqne

您可能对 Python 序列对象或与此相关的任何对象如何在遇到运算符时知道该怎么办感到好奇。 例如,表达式imdb_score * 2.5如何知道将序列中的每个元素乘以2.5? Python 使用特殊方法为对象与运算符通信提供了一种内置的标准化方法。

特殊方法是对象在遇到运算符时在内部调用的方法。 特殊方法在 Python 数据模型中定义,这是官方文档中非常重要的一部分,并且对于整个语言中的每个对象都是相同的。 特殊方法始终以两个下划线开头和结尾。 例如,每当使用乘法运算符时,就会调用特殊方法__mul__。 Python 将imdb_score * 2.5表达式解释为imdb_score.__mul__(2.5)

使用特殊方法和使用运算符之间没有什么区别,因为它们在做完全相同的事情。 运算符只是特殊方法的语法糖。

另见

将序列方法链接在一起

在 Python 中,每个变量都是一个对象,并且所有对象都具有引用或返回更多对象的属性和方法。 使用点符号的方法的顺序调用称为方法链接。 Pandas 是一个很适合进行方法链接的库,因为许多序列和数据帧方法返回更多的序列和数据帧,因此可以调用更多方法。

准备

为了激励方法链接,让我们用一个简单的英语句子将事件链转换为方法链。 考虑一下句子,“一个人开车去商店买食物,然后开车回家,在洗碗之前准备,做饭,上菜和吃食物”。

该句子的 Python 版本可能采用以下形式:

>>> person.drive('store')\
          .buy('food')\
          .drive('home')\
          .prepare('food')\
          .cook('food')\
          .serve('food')\
          .eat('food')\
          .cleanup('dishes')

在前面的代码中,person是调用每个方法的对象,就像人正在执行原始句子中的所有动作一样。 传递给每个方法的参数指定方法的操作方式。

尽管可以在单个连续的行中写入整个方法链,但更可取的是在每行中写入一个方法。 由于 Python 通常不允许将一个表达式写在多行上,因此您需要使用反斜杠行继续符。 或者,您可以将整个表达式用括号括起来。 为了进一步提高可读性,请将每种方法直接放在其上方的点下。 此秘籍显示了与 Pandas 序列相似的方法链接。

操作步骤

  1. 加载电影数据集,然后选择两列作为不同的序列:
>>> movie = pd.read_csv('data/movie.csv')
>>> actor_1_fb_likes = movie['actor_1_facebook_likes']
>>> director = movie['director_name']
  1. 附加到链上的最常见方法之一是head方法。 这抑制了长输出。 对于较短的链,则不需要将每种方法放在不同的行上:
>>> director.value_counts().head(3)
Steven Spielberg    26
Woody Allen         22
Clint Eastwood      20
Name: director_name, dtype: int64
  1. 计算缺失值数量的一种常见方法是在isnull之后链接sum方法:
>>> actor_1_fb_likes.isnull().sum()
7
  1. actor_1_fb_likes的所有非缺失值都应为整数,因为不可能有小数 Facebook 点赞。 缺少值的任何数字列的数据类型都必须为float。 如果我们用零填充actor_1_fb_likes中的缺失值,则可以使用astype方法将其转换为整数:
>>> actor_1_fb_likes.dtype
dtype('float64')

>>> actor_1_fb_likes.fillna(0)\
                    .astype(int)\
                    .head()
0     1000
1    40000
2    11000
3    27000
4      131
Name: actor_1_facebook_likes, dtype: int64

工作原理

所有的 Python 对象都可以进行方法链接,因为每个对象方法必须返回另一个自身将具有更多方法的对象。 该方法不必返回相同类型的对象。

步骤 2 首先使用value_counts返回一个序列,然后链接head方法以选择前三个元素。 最后返回的对象是一个序列,也可以在其上链接更多方法。

在步骤 3 中,isnull方法创建一个布尔序列。 Pandas 在数值上将False/True求值为 0/1,因此sum方法返回缺失值的数量。

步骤 4 中的三个链接方法中的每一个都返回一个序列。 似乎不直观,但是astype方法返回具有不同数据类型的全新序列。

更多

无需对第 3 步中的布尔值求和以找到缺失值的总数,我们可以采用序列的平均值来获取缺失值的百分比:

>>> actor_1_fb_likes.isnull().mean()
0.0014

如本秘籍开头所述,对于多行代码,可以使用括号而不是反斜杠。 第 4 步可以这样重写:

>>> (actor_1_fb_likes.fillna(0)
                     .astype(int)
                     .head())

并不是所有的程序员都喜欢使用方法链接,因为它有一些缺点。 这样的缺点之一是调试变得困难。 链中产生的中间对象都不存储在变量中,因此,如果出现意外结果,将很难跟踪链中发生它的确切位置。

秘籍开头的示例可以重写,以使每种方法的结果都保存为唯一变量。 这使跟踪错误更加容易,因为您可以在每个步骤检查对象:

>>> person1 = person.drive('store')
>>> person2 = person1.buy('food')
>>> person3 = person2.drive('home')
>>> person4 = person3.prepare('food')
>>> person5 = person4.cook('food')
>>> person6 = person5.serve('food')
>>> person7 = person6.eat('food')
>>> person8 = person7.cleanup('dishes')

使索引有意义

数据帧的索引为每行提供一个标签。 如果在创建数据帧时未显式提供索引,则默认情况下,将创建RangeIndex,其标签为从 0 到n-1的整数,其中 n 是行数。

准备

此秘籍将影片数据集的毫无意义的默认行索引替换为影片标题,这更有意义。

操作步骤

  1. 读取电影数据集,然后使用set_index方法将每部电影的标题设置为新索引:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie2 = movie.set_index('movie_title')
>>> movie2
  1. 或者,可以通过read_csv函数的index_col参数在初始读取时选择一列作为索引:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')

工作原理

一个有意义的索引是清楚地标识每一行的索引。 默认的范围索引不是很有帮助。 由于每一行仅标识一部电影的数据,因此使用电影标题作为标签是有意义的。 如果您提前知道哪个列将是一个很好的索引,则可以在导入时使用read_csv函数的index_col参数指定该索引。

默认情况下,set_indexread_csv都将从数据帧中删除用作索引的列。 使用set_index,可以通过将drop参数设置为False将列保留在数据帧中。

更多

相反,可以使用reset_index方法将索引变成一列。 这将使movie_title再次成为一列,并将索引还原回RangeIndexreset_index始终将列作为数据帧中的第一个列,因此这些列可能未按其原始顺序排列:

>>> movie2.reset_index()

另见

重命名行和列名称

数据帧上最基本,最常见的操作之一是重命名行或列的名称。 好的列名是描述性的,简短的,并且在大小写,空格,下划线和其他功能方面遵循通用约定。

准备

在此秘籍中,行名和列名均被重命名。

操作步骤

  1. 读取电影数据集,并通过将其设置为电影标题来使索引有意义:
>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
  1. 数据帧的rename方法接受将旧值映射到新值的字典。 让我们为行创建一个,为列创建另一个:
>>> idx_rename = {'Avatar':'Ratava', 'Spectre': 'Ertceps'} 
>>> col_rename = {'director_name':'Director Name', 
                  'num_critic_for_reviews': 'Critical Reviews'} 
  1. 将字典传递给rename方法,并将结果分配给新变量:
>>> movie_renamed = movie.rename(index=idx_rename, 
                                 columns=col_rename)
>>> movie_renamed.head()

工作原理

数据帧的rename方法允许使用indexcolumns参数同时重命名行标签和列标签。 这些参数中的每一个都可以设置为字典,该字典将旧标签映射到它们的新值。

更多

重命名行标签和列标签有多种方法。 可以直接将索引和列属性重新分配给 Python 列表。 当列表具有与行和列标签相同数量的元素时,此分配有效。 以下代码在每个索引对象上使用tolist方法来创建 Python 标签列表。 然后,它修改列表中的几个值,并将列表重新分配给属性indexcolumns

>>> movie = pd.read_csv('data/movie.csv', index_col='movie_title')
>>> index = movie.index
>>> columns = movie.columns

>>> index_list = index.tolist()
>>> column_list = columns.tolist()

## rename the row and column labels with list assignments
>>> index_list[0] = 'Ratava'
>>> index_list[2] = 'Ertceps'
>>> column_list[1] = 'Director Name'
>>> column_list[2] = 'Critical Reviews'

>>> print(index_list)
['Ratava', "Pirates of the Caribbean: At World's End", 'Ertceps', 'The Dark Knight Rises', ... ]

>>> print(column_list)
['color', 'Director Name', 'Critical Reviews', 'duration', ...]

## finally reassign the index and columns
>>> movie.index = index_list
>>> movie.columns = column_list

创建和删除列

在数据分析期间,极有可能需要创建新列来表示新变量。 通常,这些新列将从数据集中已有的先前列创建。 Pandas 有几种不同的方法可以向数据帧添加新列。

准备

在此秘籍中,我们通过使用赋值在影片数据集中创建新列,然后使用drop方法删除列。

操作步骤

  1. 创建新列的最简单方法是为其分配标量值。 将新列的名称作为字符串放入索引运算符。 让我们在电影数据集中创建has_seen列以指示我们是否看过电影。 我们将为每个值分配零。 默认情况下,新列将追加到末尾:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie['has_seen'] = 0
  1. 有几列包含有关 Facebook 点赞次数的数据。 让我们将所有演员和导演的 Facebook 点赞数加起来,并将其分配到actor_director_facebook_likes列:
>>> movie['actor_director_facebook_likes'] =  \
        (movie['actor_1_facebook_likes'] + 
         movie['actor_2_facebook_likes'] + 
         movie['actor_3_facebook_likes'] + 
         movie['director_facebook_likes'])
  1. 从本章的“调用序列方法”秘籍中,我们知道此数据集包含缺失值。 当像上一步那样将数字列彼此相加时,pandas 将缺失值默认为零。 但是,如果缺少特定行的所有值,则 Pandas 也会将总数也保留为丢失。 让我们检查新列中是否缺少值,并用 0 填充它们:
>>> movie['actor_director_facebook_likes'].isnull().sum()
122
>>> movie['actor_director_facebook_likes'] = \
    movie['actor_director_facebook_likes'].fillna(0)
  1. 数据集中还有另一列cast_total_facebook_likes。 看到此列的百分比来自我们新创建的列actor_director_facebook_likes会很有趣。 在创建百分比列之前,我们先进行一些基本数据验证。 让我们确保cast_total_facebook_likes大于或等于actor_director_facebook_likes
>>> movie['is_cast_likes_more'] = \
         (movie['cast_total_facebook_likes'] >=             
          movie['actor_director_facebook_likes'])
  1. is_cast_likes_more现在是一列布尔值。 我们可以使用all序列方法检查此列的所有值是否均为True
>>> movie['is_cast_likes_more'].all()
False
  1. 事实证明,至少有一部电影的actor_director_facebook_likescast_total_facebook_likes多。 导演的 Facebook 点赞可能不是演员总点赞数的一部分。 让我们回溯并删除actor_director_facebook_likes列:
>>> movie = movie.drop('actor_director_facebook_likes',
                       axis='columns')
  1. 让我们重新创建一个仅包含演员总数的列:
>>> movie['actor_total_facebook_likes'] = \
         (movie['actor_1_facebook_likes'] + 
          movie['actor_2_facebook_likes'] + 
          movie['actor_3_facebook_likes'])

>>> movie['actor_total_facebook_likes'] = \
         movie['actor_total_facebook_likes'].fillna(0)
  1. 再次检查cast_total_facebook_likes中的所有值是否都大于actor_total_facebook_likes
>>> movie['is_cast_likes_more'] = \
         (movie['cast_total_facebook_likes'] >= 
          movie['actor_total_facebook_likes'])

>>> movie['is_cast_likes_more'].all()
True
  1. 最后,让我们计算来自actor_total_facebook_likescast_total_facebook_likes的百分比:
>>> movie['pct_actor_cast_like'] = \
         (movie['actor_total_facebook_likes'] / 
          movie['cast_total_facebook_likes'])
  1. 让我们验证此列的最小值和最大值在 0 到 1 之间:
>>> (movie['pct_actor_cast_like'].min(), 
     movie['pct_actor_cast_like'].max())
(0.0, 1.0)
  1. 然后,我们可以将该列输出为序列。 首先,我们需要将索引设置为电影标题,以便我们可以正确识别每个值。
>>> movie.set_index('movie_title')['pct_actor_cast_like'].head()
movie_title
Avatar                                        0.577369
Pirates of the Caribbean: At World's End      0.951396
Spectre                                       0.987521
The Dark Knight Rises                         0.683783
Star Wars: Episode VII - The Force Awakens    0.000000
Name: pct_actor_cast_like, dtype: float64

工作原理

许多 Pandas 操作都很灵活,列创建就是其中之一。 该秘籍既分配了标量值(如步骤 1 所示),又分配了序列(如步骤 2 所示),以创建新列。

步骤 2 将四个不同的序列使用加法运算符相加。 步骤 3 使用方法链来查找和填充缺失值。 步骤 4 使用大于或等于比较运算符返回布尔序列,然后在步骤 5 中使用all方法对其进行求值,以检查每个单个值是否为True

drop方法接受要删除的行或列的名称。 默认情况下是按索引名称删除行。 要删除列,必须将axis参数设置为 1 或column。 轴的默认值为 0 或字符串index

步骤 7 和 8 在没有director_facebook_likes列的情况下将步骤 3 的工作重做到步骤 5。 第 9 步最终计算出自第 4 步以来我们想要的期望列。第 10 步验证百分比在 0 到 1 之间。

更多

除了insert方法的末尾,还可以将新列插入数据帧中的特定位置。insert方法将新列的整数位置作为第一个参数,将新列的名称作为第二个参数,并将值作为第三个参数。 您将需要使用索引的get_loc方法来查找列名称的整数位置。

insert方法就地修改了调用的数据帧,因此不会有赋值语句。 可以通过从gross中减去budget并将其直接插入gross之后,来计算每部电影的利润:

>>> profit_index = movie.columns.get_loc('gross') + 1
>>> profit_index
9

>>> movie.insert(loc=profit_index,
                 column='profit',
                 value=movie['gross'] - movie['budget'])

使用drop方法删除列的另一种方法是使用del语句:

>>> del movie['actor_director_facebook_likes']

另见

  • 请参阅第 9 章,“组合 Pandas 对象”的“对数据帧添加新行”秘籍,来添加和删除行,这是一种较不常用的操作
  • 请参阅第 3 章,“开始数据分析”的“制定数据分析例程”秘籍。

二、数据帧基本操作

在本章中,我们将介绍以下主题:

  • 选择数据帧的多个列
  • 用方法选择列
  • 明智地排序列名称
  • 处理整个数据帧
  • 将数据帧方法链接在一起
  • 将运算符与数据帧一起使用
  • 比较缺失值
  • 转换数据帧操作的方向
  • 确定大学校园的多样性

介绍

本章介绍了数据帧的许多基本操作。 许多秘籍将与第 1 章,“Pandas 基础”中的内容类似,这些内容主要涵盖序列操作。

选择数据帧的多个列

选择单个列是通过将所需的列名作为字符串传递给数据帧的索引运算符来完成的。 在第 1 章,“Pandas 基础”的“选择序列”秘籍中对此进行了介绍。 通常需要关注当前工作数据集的一个子集,这是通过选择多个列来完成的。

准备

在此秘籍中,将从movie数据集中选择所有actordirector列。

操作步骤

  1. 读取电影数据集,并将所需列的列表传递给索引运算符:
>>> movie_actor_director = movie[['actor_1_name', 'actor_2_name',
                                  'actor_3_name', 'director_name']]
>>> movie_actor_director.head()

  1. 在某些情况下,需要选择数据帧的一列。 这是通过将单个元素列表传递给索引运算符来完成的:
>>> movie[['director_name']].head()

工作原理

数据帧的索引运算符非常灵活,可以接受许多不同的对象。 如果传递了字符串,它将返回一维序列。 如果将列表传递给索引运算符,它将以指定顺序返回列表中所有列的数据帧。

步骤 2 显示了如何选择单个列作为数据帧而不是序列。 最常见的是,使用字符串选择单个列,从而得到一个序列。 当数据帧是所需的输出时,只需将列名放在一个单元素列表中。

更多

在索引运算符内部传递长列表可能会导致可读性问题。 为了解决这个问题,您可以先将所有列名保存到列表变量中。 下面的代码获得与步骤 1 相同的结果:

>>> cols = ['actor_1_name', 'actor_2_name',
            'actor_3_name', 'director_name']
>>> movie_actor_director = movie[cols]

KeyError是处理 Pandas 的最常见例外之一。 此错误主要是由于列名或索引名的错误输入。 每当尝试不使用列表进行多列选择时,都会引发相同的错误:

>>> movie['actor_1_name', 'actor_2_name',
          'actor_3_name', 'director_name']
KeyError: ('actor_1_name', 'actor_2_name',
           'actor_3_name', 'director_name')

这是一个常见的错误,因为很容易忘记将所需的列放在列表中。 您可能想知道这里到底发生了什么。 技术上,用逗号分隔的四个字符串名称是一个元组对象。 通常,元组用开括号和闭括号括起来,但这不是必需的:

>>> tuple1 = 1, 2, 3, 'a', 'b'
>>> tuple2 = (1, 2, 3, 'a', 'b')
>>> tuple1 == tuple2
True

Pandas 正试图找到与元组('actor_1_name', 'actor_2_name', 'actor_3_name', 'director_name')完全相同的列名。 它失败并引发KeyError

用方法选择列

尽管列选择通常直接由索引运算符完成,但是有一些数据帧方法可以以替代方式方便其选择。select_dtypesfilter是执行此操作的两种有用方法。

准备

您需要熟悉所有 Pandas 数据类型以及如何访问它们。 第 1 章,“Pandas 基础”中的“了解数据类型”秘籍具有包含所有 Pandas 数据类型的表。

工作原理

  1. 读入电影数据集,并使用电影的标题标记每一行。 使用get_dtype_counts方法输出每种特定数据类型的列数:
>>> movie = pd.read_csv('data/movie.csv',
                        index_col='movie_title')
>>> movie.get_dtype_counts()
float64    13
int64       3
object     11
dtype: int64
  1. 使用select_dtypes方法仅选择整数列:
>>> movie.select_dtypes(include=['int']).head()

  1. 如果要选择所有数字列,则只需将字符串数字传递给include参数:
>>> movie.select_dtypes(include=['number']).head()

  1. 选择列的另一种方法是使用filter方法。 此方法很灵活,可以根据使用的参数搜索列名(或索引标签)。 在这里,我们使用like参数搜索包含确切字符串facebook的所有列名称:
>>> movie.filter(like='facebook').head()

  1. filter方法允许使用regex参数通过正则表达式搜索列。 在这里,我们搜索名称中某处有数字的所有列:
>>> movie.filter(regex='\d').head()

工作原理

步骤 1 列出了所有不同数据类型的频率。 或者,您可以使用dtypes属性来获取每一列的确切数据类型。select_dtypes方法在其include参数中获取数据类型的列表,并返回仅包含那些给定数据类型的列的数据帧。 列表值可以是数据类型的字符串名称,也可以是实际的 Python 对象。

filter方法仅通过检查列名而不是实际数据值来选择列。 它具有三个互斥的参数itemslikeregex,一次只能使用其中一个。like参数采用一个字符串,并尝试查找名称中某处包含该确切字符串的所有列名称。 为了获得更大的灵活性,您可以使用regex参数代替通过正则表达式选择列名称。 这个特定的正则表达式\d表示从零到九的所有数字,并且匹配其中至少包含一个数字的任何字符串。

正则表达式是代表搜索模式的字符序列,这些搜索模式用于选择文本的不同部分。 它们允许非常复杂和高度特定的模式匹配。

更多

filter方法带有另一个参数items,该参数采用一列确切的列名。 这几乎与索引运算符完全相同,只是如果其中一个字符串与列名不匹配,则不会引发KeyError。 例如,movie.filter(items=['actor_1_name', 'asdf'])运行无错误,并返回单列数据帧。

select_dtypes的一个令人困惑的方面是它同时接受字符串和 Python 对象的灵活性。 下表应阐明选择许多不同列数据类型的所有可能方法。 在 Pandas 中没有引用数据类型的标准或首选方法,因此最好同时了解两种方式:

Python 对象字符串注释
np.numbernumber选择整数和浮点数,而不考虑大小
np.float64, np.float_, floatfloat64float_float仅选择 64 位浮点数
np.float16, np.float32, np.float128float16float32float128分别选择精确的 16 位,32 位和 128 位浮点数
np.floatingfloating选择所有浮点,而不管大小
np.int0, np.int64, np.int_, intint0int64int_int仅选择 64 位整数
np.int8, np.int16, np.int32int8int16int32分别选择 8、16 和 32 位整数
np.integerinteger选择所有整数,而不考虑大小
np.objectobjectO选择所有对象数据类型
np.datetime64datetime64datetime所有 64 位的日期时间
np.timedelta64timedelta64timedelta所有 64 位的时间增量
pd.CategoricalcategoryPandas 特有的; NumPy 没有等效的东西

因为所有整数和浮点数默认为 64 位,所以可以通过使用字符串int,或float来选择它们,如上表所示。 如果要选择所有整数和浮点数,而不管它们的大小如何,请使用字符串number

另见

明智地排序列名称

最初将数据集导入为数据帧之后要考虑的首要任务之一是分析列的顺序。 这个基本任务经常被忽略,但是可以在分析进行中产生很大的不同。 计算机没有优先选择列顺序,计算也不受影响。 作为人类,我们自然地从左到右查看和阅读列,这直接影响我们对数据的解释。 杂物柱布置类似于壁橱中的杂物衣服布置。 在短裤顶部的衬衫和裤子旁边放西装是没有好处的。 考虑列顺序时,查找和解释信息要容易得多。

没有标准的规则集来规定应如何在数据集中组织列。 但是,优良作法是制定一组您始终遵循的准则以简化分析。 如果您与一组共享大量数据集的分析师合作,则尤其如此。

准备

以下是排序列的简单指南:

  • 将每列分为离散列或连续列
  • 在离散列和连续列中将公共列分组
  • 将最重要的列组首先放置在分类列之前,然后再放置连续列

本秘籍向您展示如何使用此指南排序各列。 有许多明智的可能排序。

操作步骤

  1. 读取电影数据集,然后扫描数据:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie.head()

  1. 输出所有列名称并扫描相似的离散列和连续列:
>>> movie.columns
Index(['color', 'director_name', 'num_critic_for_reviews',
       'duration', 'director_facebook_likes',
       'actor_3_facebook_likes', 'actor_2_name',
       'actor_1_facebook_likes', 'gross', 'genres',
       'actor_1_name', 'movie_title', 'num_voted_users',
       'cast_total_facebook_likes', 'actor_3_name',
       'facenumber_in_poster', 'plot_keywords',
       'movie_imdb_link', 'num_user_for_reviews', 'language',
       'country', 'content_rating', 'budget', 'title_year',
       'actor_2_facebook_likes', 'imdb_score', 'aspect_ratio',
       'movie_facebook_likes'], dtype='object')
  1. 这些列似乎没有任何逻辑顺序。 将名称合理地组织到列表中,以便遵循上一部分的指南:
>>> disc_core = ['movie_title', 'title_year',
                 'content_rating', 'genres']
>>> disc_people = ['director_name', 'actor_1_name', 
                   'actor_2_name', 'actor_3_name']
>>> disc_other = ['color', 'country', 'language', 
                  'plot_keywords', 'movie_imdb_link']

>>> cont_fb = ['director_facebook_likes', 'actor_1_facebook_likes', 
               'actor_2_facebook_likes', 'actor_3_facebook_likes',
               'cast_total_facebook_likes', 'movie_facebook_likes']

>>> cont_finance = ['budget', 'gross']
>>> cont_num_reviews = ['num_voted_users', 'num_user_for_reviews',
                        'num_critic_for_reviews']
>>> cont_other = ['imdb_score', 'duration',
                  'aspect_ratio', 'facenumber_in_poster']
  1. 将所有列表连接在一起以获得最终的列顺序。 另外,请确保此列表包含原始文档中的所有列:
>>> new_col_order = disc_core + disc_people + \
                    disc_other + cont_fb + \
                    cont_finance + cont_num_reviews + \
                    cont_other
>>> set(movie.columns) == set(new_col_order)
True
  1. 将具有新列顺序的列表传递给数据帧的索引运算符以对列进行重新排序:
>>> movie2 = movie[new_col_order]
>>> movie2.head()

工作原理

要从数据帧中选择列的子集,请使用特定列名称的列表。 例如,movie[['movie_title', 'director_name']]仅使用movie_titledirector_name列创建一个新的数据帧。 通过名称选择列是 Pandas 数据帧的索引运算符的默认行为。

步骤 3 根据类型(离散或连续)以及它们的数据相似程度,将所有列名称整齐地组织到单独的列表中。 最重要的列(例如电影的标题)位于第一位。

步骤 4 连接所有列名称列表,并验证此新列表是否包含与原始列名称相同的值。 Python 集是无序的,并且相等语句检查一个集的每个成员是否是另一个集的成员。 手动排序此秘籍中的列容易受到人为错误的影响,因为很容易错误地忘记新列列表中的列。

步骤 5 通过将新的列顺序作为列表传递给索引运算符来完成重新排序。 现在,这个新顺序比原来的要明智得多。

更多

除了前面提到的简单建议外,还有其他排序列的准则。 Hadley Wickham 在有关整洁数据的开创性论文中建议将固定变量放在第一位,然后再放置测量变量。 由于此数据并非来自受控实验,因此可以灵活地确定哪些变量是固定的,哪些是测量的。 测量变量的良好候选者是我们希望预测的变量,例如gross,总收入或imdb_score。 例如,以这种顺序,我们可以混合离散变量和连续变量。 在该演员的名字之后直接放置 Facebook 点赞人数的列可能更有意义。 当然,由于计算部分不受列顺序的影响,因此您可以提出自己的列顺序准则。

通常,您将直接从关系数据库中提取数据。 关系数据库的一种非常常见的做法是将主键(如果存在)作为第一列,并在其后直接放置任何外键。

主键唯一地标识当前表中的行。 外键唯一地标识其他表中的行。

另见

处理整个数据帧

在第 1 章,“Pandas 基础”的“调用序列方法”秘籍中,对单列或序列数据进行操作的各种方法。 当从数据帧调用这些相同的方法时,它们会立即对每一列执行该操作。

准备

在本秘籍中,我们将对电影数据集探索各种最常见的数据帧属性和方法。

操作步骤

  1. 阅读电影数据集,并获取基本描述性属性shapesizendim,以及运行len函数:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie.shape
(4916, 28)

>>> movie.size
137648

>>> movie.ndim
2

>>> len(movie)
4916
  1. 使用count方法查找每列的不丢失值的数量。 输出是一个序列,现在其旧列名称为,其索引为:
>>> movie.count()
color                     4897
director_name             4814
num_critic_for_reviews    4867
duration                  4901
                          ... 
actor_2_facebook_likes    4903
imdb_score                4916
aspect_ratio              4590
movie_facebook_likes      4916
Length: 28, dtype: int64
  1. 其他计算摘要统计信息的方法,例如minmaxmeanmedianstd都返回相似的序列,其索引中的列名称及其计算结果为值:
>>> movie.min()
num_critic_for_reviews     1.00
duration                   7.00
director_facebook_likes    0.00
actor_3_facebook_likes     0.00
                           ... 
actor_2_facebook_likes     0.00
imdb_score                 1.60
aspect_ratio               1.18
movie_facebook_likes       0.00
Length: 16, dtype: float64
  1. describe方法非常强大,可以一次计算前面步骤中的所有描述性统计数据和四分位数。 最终结果是一个数据帧,其描述性统计信息为,其索引为:
>>> movie.describe()

  1. 可以使用percentiles参数在describe方法中指定精确的分位数:
>>> movie.describe(percentiles=[.01, .3, .99])

工作原理

步骤 1 提供有关数据集大小的基本信息。shape属性返回行和列数的两个元素的元组。size属性返回数据帧中元素的总数,它只是行和列数的乘积。ndim属性返回维数,对于所有数据帧,维数均为 2。 Pandas 定义了内置的len函数以返回行数。

步骤 2 和步骤 3 中的方法将每一列汇总为一个数字。 现在,每个列名称都是序列中的索引标签,其汇总结果为相应的值。

如果仔细观察,您会发现步骤 3 的输出缺少步骤 2 的所有对象列。其原因是对象列中缺少值,而 pandas 不知道如何处理字符串值与缺失值。 它会静默删除无法为其计算最小值的所有列。

在这种情况下,静默意味着没有引发任何错误并且没有发出警告。 这有点危险,需要用户熟悉 Pandas。

数字列也缺少值,但返回了结果。 默认情况下,pandas 通过跳过数值列来处理缺失值。 通过将skipna参数设置为False可以更改此行为。 如果存在至少一个缺失值,这将导致所有这些聚合方法的 Pandas 返回NaN

describe方法可一次显示所有主要摘要,并且可以通过将 0 到 1 之间的数字列表传递给percentiles参数来扩展其摘要以包含更多分位数。 默认情况下,仅在数字列上显示信息。 有关describe方法的更多信息,请参见“开发数据分析例程”秘籍。

更多

要查看skipna参数如何影响结果,我们可以将其值设置为False,然后从前面的秘籍重新运行步骤 3。 只有没有缺失值的数字列将计算结果:

>>> movie.min(skipna=False)
num_critic_for_reviews     NaN
duration                   NaN
director_facebook_likes    NaN
actor_3_facebook_likes     NaN
                          ... 
actor_2_facebook_likes     NaN
imdb_score                 1.6
aspect_ratio               NaN
movie_facebook_likes       0.0
Length: 16, dtype: float64

将数据帧方法链接在一起

无论您相信方法链接是否是一种好的做法,在使用 Pandas 进行数据分析时都会遇到它是很普遍的。 第 1 章,“Pandas 基础”中的“将序列方法链接在一起”秘籍展示了链接序列方法一起的几个示例。 本章中的所有方法链都将从数据帧开始。 方法链接的关键之一是知道在链接的每个步骤中返回的确切对象。 在 Pandas 中,这几乎总是一个数据帧,序列或标量值。

准备

在此秘籍中,我们计算移动数据集每一列中的所有缺失值。

操作步骤

  1. 要获得缺失值的计数,必须首先调用isnull方法以将每个数据帧值更改为布尔值。 让我们在电影数据集上调用此方法:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie.isnull().head()

  1. 我们将链接将True/False布尔值解释为 1/0 的sum方法。 请注意,返回了一个序列:
>>> movie.isnull().sum().head()
color                       19
director_name              102
num_critic_for_reviews      49
duration                    15
director_facebook_likes    102
dtype: int64
  1. 我们可以再走一步,取该序列的总和,然后将整个数据帧中缺失值总数的计数作为标量值返回:
>>> movie.isnull().sum().sum()
2654
  1. 略有偏差是为了确定数据帧中是否缺少任何值。 我们在此连续两次使用any方法来执行此操作:
>>> movie.isnull().any().any()
True

工作原理

isnull方法返回一个与调用数据帧相同大小的数据帧,但所有值都转换为布尔值。 请参阅以下数据类型的计数以验证这一点:

>>> movie.isnull().get_dtype_counts()
bool    28
dtype: int64

由于布尔值的数值求值为 0/1,因此可以按列对它们进行求和,如步骤 2 所示。所得的序列本身也具有sum方法,该方法可以使我们在数据帧中获得总计的缺失值。

在步骤 4 中,数据帧的any方法返回布尔值序列,指示每个列是否存在至少一个Trueany方法再次链接到该布尔结果序列上,以确定是否有任何列缺少值。 如果步骤 4 求值为True,则整个数据帧中至少存在一个缺失值。

更多

电影数据集中具有对象数据类型的大多数列都包含缺少的值。 默认情况下,聚合方法minmaxsum不返回任何内容,如以下代码片段所示,该代码片段选择三个对象列并尝试查找每个对象的最大值:

>>> movie[['color', 'movie_title', 'color']].max()
Series([], dtype: float64)

为了迫使 Pandas 为每一列返回值,我们必须填写缺失值。 在这里,我们选择一个空字符串:

>>> movie.select_dtypes(['object']).fillna('').min()
color                                                          Color
director_name                                          Etienne Faure
actor_2_name                                           Zubaida Sahar
genres                                                       Western
actor_1_name                                           Oscar Jaenada
movie_title                                                 Æon Flux
actor_3_name                                           Oscar Jaenada
plot_keywords                                    zombie|zombie spoof
movie_imdb_link    http://www.imdb.com/title/tt5574490/?ref_=fn_t...
language                                                        Zulu
country                                                 West Germany
content_rating                                                     X
dtype: object

出于可读性考虑,方法链通常被编写为每行一个方法调用,并在末尾使用反斜杠字符以避开新行。 这样可以更轻松地阅读和插入有关链的每个步骤返回的内容的注释:

>>> # rewrite the above chain on multiple lines
>>> movie.select_dtypes(['object']) \
         .fillna('') \
         .min()

由于未统一定义最小值和最大值,因此汇总所有字符串的列是无类型。 尝试调用明显没有字符串解释的方法,例如查找均值或方差,将无法正常工作。

另见

  • 参考第 1 章,“Pandas 基础”中的“将序列方法链接到一起”秘籍

将运算符与数据帧一起使用

它与第 1 章,“Pandas 基础”的秘籍有关,其中提供了关于运算符的入门知识。 这里。 Python 算术和比较运算符直接在数据帧上工作,就像在序列上一样。

准备

当数据帧直接使用算术运算符或比较运算符之一进行运算时,每列的每个值都会对其应用运算。 通常,当运算符与数据帧一起使用时,列要么全为数字,要么为所有对象(通常是字符串)。 如果数据帧不包含同类数据,则该操作很可能会失败。 让我们来看一个关于大学数据集失败的示例,该数据集同时包含数字和对象数据类型。 尝试将5添加到数据帧的每个值都会引发TypeError,因为不能将整数添加到字符串中:

>>> college = pd.read_csv('data/college.csv')
>>> college + 5
TypeError: Could not operate 5 with block values must be str, not int

若要成功将运算符与数据帧配合使用,请首先选择同类数据。 对于此秘籍,我们将选择以UGDS_开头的所有列。 这些栏代表按种族划分的大学生比例。 首先,我们导入数据并使用机构名称作为索引的标签,然后使用filter方法选择所需的列:

>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds_ = college.filter(like='UGDS_')
>>> college_ugds_.head()

此秘籍使用多个运算符和一个数据帧将本科生的列四舍五入到最接近的百分之一。 然后,我们将看到此结果如何等效于round方法。

操作步骤

  1. 为了与运算符开始四舍五入的冒险,我们首先将.00501添加到college_ugds_的每个值:
>>> college_ugds_ + .00501

  1. 使用楼层除法运算符//舍入到最接近的整数百分比:
>>> (college_ugds_ + .00501) // .01

  1. 要完成舍入练习,请除以 100:
>>> college_ugds_op_round = (college_ugds_ + .00501) // .01 / 100
>>> college_ugds_op_round.head()

  1. 现在,使用数据帧的round方法为我们自动进行舍入。 NumPy 四舍五入正好在两边到偶数边中间的数字。 因此,我们在舍入前添加一小部分:
>>> college_ugds_round = (college_ugds_ + .00001).round(2)
  1. 使用数据帧的equals方法测试两个数据帧的相等性:
>>> college_ugds_op_round.equals(college_ugds_round)
True

工作原理

步骤 1 使用加法运算符,该运算符尝试将标量值添加到数据帧的每一列的每个值。 由于列都是数字,因此此操作按预期进行。 每列中都有一些缺失值,但在操作后它们仍然缺失。

从数学上讲,添加.005应该足够,以便下一步的底数分割正确舍入到最接近的整数百分比。 由于浮点数的不精确性而出现问题:

>>> .045 + .005
0.049999999999999996

每个数字都有一个额外的.00001,以确保浮点表示的前四位数字与实际值相同。 之所以可行,是因为数据集中所有点的最大精度是四个小数位。

步骤 2 将楼层除法运算符//应用于数据帧中的所有值。 实际上,当我们除以小数时,它是将每个值乘以100并截断任何小数。 在表达式的第一部分周围需要括号,因为底数划分的优先级高于加法。 步骤 3 使用除法运算符将小数返回正确的位置。

在步骤 4 中,我们使用round方法重现了先前的步骤。 在执行此操作之前,由于与步骤 1 有所不同的原因,我们必须再次向每个数据帧值添加一个额外的.00001。NumPy 和 Python 3 的舍入数字恰好位于两边到偶数之间。 这种与偶数技术的联系通常不是学校正式教的。 它不会始终将数字偏向更高端

这里有必要四舍五入,以使两个数据帧值相等。equals方法确定两个数据帧之间的所有元素和索引是否完全相同,并返回一个布尔值。

更多

与序列一样,数据帧具有与运算符等效的方法。 您可以将运算符替换为其等效的方法:

>>> college_ugds_op_round_methods = college_ugds_.add(.00501) \
                                                 .floordiv(.01) \
                                                 .div(100)
>>> college_ugds_op_round_methods.equals(college_ugds_op_round)
True

另见

比较缺失值

Pandas 使用 NumPy NaN(np.nan)对象表示缺失值。 这是不寻常的对象,因为它不等于其自身。 与自身相比,甚至 Python 的None对象也将其求值为True

>>> np.nan == np.nan
False
>>> None == None
True

np.nan的所有其他比较也返回False,除了不等于:

>>> np.nan > 5
False
>>> 5 > np.nan
False
>>> np.nan != 5
True

准备

序列和数据帧使用等号运算符==进行逐元素比较,以返回相同大小的对象。 此秘籍向您展示如何使用相等运算符,该运算符与equals方法非常不同。

与前面的秘籍一样,将使用代表大学数据集中各种族学生的分数的列:

>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds_ = college.filter(like='UGDS_')

操作步骤

  1. 为了了解相等运算符的工作原理,我们将每个元素与一个标量值进行比较:
>>> college_ugds_ == .0019

  1. 这可以按预期工作,但是每当您尝试比较缺少值的数据帧时,就会出现问题。 该相同的等于运算符可用于在逐个元素的基础上将两个数据帧相互比较。 例如,将college_ugds_与自身进行比较,如下所示:
>>> college_self_compare = college_ugds_ == college_ugds_
>>> college_self_compare.head()

  1. 乍一看,所有值似乎都相等,就像您期望的那样。 但是,使用all方法确定每列是否仅包含True值会产生意外结果:
>>> college_self_compare.all()
UGDS_WHITE    False
UGDS_BLACK    False
UGDS_HISP     False
UGDS_ASIAN    False
UGDS_AIAN     False
UGDS_NHPI     False
UGDS_2MOR     False
UGDS_NRA      False
UGDS_UNKN     False
dtype: bool
  1. 发生这种情况是因为缺失值彼此之间没有相等的比较。 如果您尝试使用相等运算符对缺失值进行计数并对布尔列求和,则每个数字将得到零:
>>> (college_ugds_ == np.nan).sum()
UGDS_WHITE    0
UGDS_BLACK    0
UGDS_HISP     0
UGDS_ASIAN    0
UGDS_AIAN     0
UGDS_NHPI     0
UGDS_2MOR     0
UGDS_NRA      0
UGDS_UNKN     0
dtype: int64
  1. 计算缺失值的主要方法是使用isnull方法:
>>> college_ugds_.isnull().sum()
UGDS_WHITE    661
UGDS_BLACK    661
UGDS_HISP     661
UGDS_ASIAN    661
UGDS_AIAN     661
UGDS_NHPI     661
UGDS_2MOR     661
UGDS_NRA      661
UGDS_UNKN     661
dtype: int64
  1. 比较两个整个数据帧的正确方法不是使用相等运算符,而是使用equals方法:
>>> college_ugds_.equals(college_ugds_)
True

工作原理

步骤 1 将一个数据帧与一个标量值进行比较,而步骤 2 将一个数据帧与另一个数据帧进行比较。 乍看之下,这两种操作都非常简单直观。 第二个操作实际上是检查数据帧是否具有相同标签的索引,以及是否具有相同数量的元素。 如果不是这种情况,操作将失败。 有关更多信息,请参见第 6 章,“索引对齐”中的“生成笛卡尔积”秘籍。

步骤 3 验证数据帧中的列均不相等。 步骤 4 进一步显示了np.nan与它本身的不等价性。 步骤 5 验证数据帧中确实存在缺失值。 最后,第 6 步显示了将数据帧与equals方法进行比较的正确方法,该方法始终返回布尔型标量值。

更多

所有比较运算符都有对应的方法,可以使用更多功能。 有点令人困惑的是,数据帧的eq方法像相等运算符一样进行逐元素比较。eq方法与equals方法完全不同。 它仅执行与相等运算符相似的任务。 以下代码重复了步骤 1:

>>> college_ugds_.eq(.0019)    # same as college_ugds_ == .0019 

pandas.testing子包中,存在开发人员在创建单元测试时必须使用的函数。 如果两个数据帧不相等,则assert_frame_equal函数将引发AssertionError。 如果传递的两个帧相等,则返回None

>>> from pandas.testing import assert_frame_equal
>>> assert_frame_equal(college_ugds_, college_ugds_) 

单元测试是软件开发中非常重要的部分,并确保代码正确运行。 Pandas 包含成千上万的单元测试,可帮助确保其正常运行。 要了解有关 Pandas 如何运行其单元测试的更多信息,请参阅文档中的“对 Pandas 做贡献”部分。

转换数据帧操作的方向

许多数据帧方法都有一个axis参数。 这个重要的参数控制操作的方向。 轴参数只能是两个值之一(0 或 1),并且分别作为字符串indexcolumn的别名。

准备

几乎所有的数据帧方法都将axis参数默认为0/index。 此秘籍向您展示了如何调用相同的方法,但其操作方向已被调换。 为了简化练习,将仅使用引用大学数据集中每个学校的百分比种族的列。

操作步骤

  1. 读取大学数据集; 以UGDS_开头的列代表特定种族的本科生所占的百分比。 使用filter方法选择以下列:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds_ = college.filter(like='UGDS_')
>>> college_ugds_.head()

  1. 现在,数据帧包含均匀的列数据,可以在垂直和水平方向上合理地进行操作。count方法返回非缺失值的数量。 默认情况下,其axis参数设置为 0:
>>> college_ugds_.count()
UGDS_WHITE    6874
UGDS_BLACK    6874
UGDS_HISP     6874
UGDS_ASIAN    6874
UGDS_AIAN     6874
UGDS_NHPI     6874
UGDS_2MOR     6874
UGDS_NRA      6874
UGDS_UNKN     6874

由于axis参数几乎总是设置为 0,因此无需执行以下操作,但出于理解的目的,第 2 步等效于college_ugds_.count(axis=0)college_ugds_.count(axis='index')

  1. axis参数更改为 1 /列,将对操作进行转置,以使每行数据都有其非缺失值的计数:
>>> college_ugds_.count(axis='columns').head()
INSTNM
Alabama A & M University               9
University of Alabama at Birmingham    9
Amridge University                     9
University of Alabama in Huntsville    9
Alabama State University               9
  1. 代替计算非缺失值,我们可以对每一行中的所有值求和。 每行百分比应总计为 1。sum方法可用于验证这一点:
>>> college_ugds_.sum(axis='columns').head()
INSTNM
Alabama A & M University               1.0000
University of Alabama at Birmingham    0.9999
Amridge University                     1.0000
University of Alabama in Huntsville    1.0000
Alabama State University               1.0000
  1. 为了了解每列的分布,可以使用median方法:
>>> college_ugds_.median(axis='index')
UGDS_WHITE    0.55570
UGDS_BLACK    0.10005
UGDS_HISP     0.07140
UGDS_ASIAN    0.01290
UGDS_AIAN     0.00260
UGDS_NHPI     0.00000
UGDS_2MOR     0.01750
UGDS_NRA      0.00000
UGDS_UNKN     0.01430

工作原理

操作的方向是 Pandas 中比较混乱的方面之一,互联网上到处都有讨论它的解释的线程。 许多新手 Pandas 用户很难记住axis参数的含义。 幸运的是,在 Pandas 中,一项操作可以完成两个潜在的方向。 一种可能的方法是尝试双向尝试直到获得所需结果的简单蛮力解决方案。 我记得axis参数的含义,认为 1 看起来像一列,对axis=1的任何操作都会返回一个新的数据列(与该列具有相同数量的项)。

这在第 3 步中得到确认,在第 3 步中,结果(没有head方法)将返回新的数据列,并且可以根据需要轻松地将其作为列附加到数据帧中。axis等于1/index的其他步骤将返回新的数据行。

更多

使用axis=1cumsum方法累积了每一行的种族百分比。 它给出的数据视图略有不同。 例如,很容易看到每所学校的白人,黑人和西班牙裔美国人的确切百分比:

>> college_ugds_cumsum = college_ugds_.cumsum(axis=1)
>>> college_ugds_cumsum.head()

另见

确定大学校园的多样性

每年都会写很多文章,讨论多样性对大学校园的不同方面和影响。 各种组织已经开发出度量标准,以尝试测量多样性。 《美国新闻》是为许多不同类别的大学提供排名的领导者,其中之一就是多样性。 他们的多样性指数排名前十的学院如下:

>> pd.read_csv('data/college_diversity.csv', index_col='School')

准备

我们的大学数据集将种族分为九个不同类别。 当尝试量化没有明显定义的事物时,例如多样性,它有助于从非常简单的事物开始。 在此秘籍中,我们的多样性指标将等于学生人数超过 15% 的种族数。

操作步骤

  1. 读入大学数据集,并仅针对大学生种族栏进行过滤:
>>> college = pd.read_csv('data/college.csv', index_col='INSTNM')
>>> college_ugds_ = college.filter(like='UGDS_')
  1. 这些大学中许多都缺少其种族列的值。 我们可以计算每一行的所有缺失值,并对所得的序列从最高到最低进行排序。 这将向大学揭示缺少值:
>>> college_ugds_.isnull()\
                 .sum(axis=1)\
                 .sort_values(ascending=False)\
                 .head()
INSTNM
Excel Learning Center-San Antonio South         9
Philadelphia College of Osteopathic Medicine    9
Assemblies of God Theological Seminary          9
Episcopal Divinity School                       9
Phillips Graduate Institute                     9
dtype: int64
  1. 既然我们已经看到了缺少所有种族列的大学,我们可以使用dropna方法删除所有缺少 9 个种族百分比的所有行。 然后,我们可以计算剩余的缺失值:
>>> college_ugds_ = college_ugds_.dropna(how='all')
>>> college_ugds_.isnull().sum()
UGDS_WHITE    0
UGDS_BLACK    0
UGDS_HISP     0
UGDS_ASIAN    0
UGDS_AIAN     0
UGDS_NHPI     0
UGDS_2MOR     0
UGDS_NRA      0
UGDS_UNKN     0
dtype: int64
  1. 数据集中没有遗漏任何值。 现在,我们可以计算多样性指标。 首先,我们将使用大于或等于数据帧的方法ge将每个值转换为布尔值:
>>> college_ugds_.ge(.15)

  1. 从这里开始,我们可以使用sum方法对每个学院的True值进行计数。 请注意,返回了一个序列:
>>> diversity_metric = college_ugds_.ge(.15).sum(axis='columns')
>>> diversity_metric.head()
INSTNM
Alabama A & M University               1
University of Alabama at Birmingham    2
Amridge University                     3
University of Alabama in Huntsville    1
Alabama State University               1
dtype: int64
  1. 为了了解分布情况,让我们在本序列中使用value_counts方法:
>>> diversity_metric.value_counts()
1    3042
2    2884
3     876
4      63
0       7
5       2
dtype: int64
  1. 令人惊讶的是,两所学校在五个不同种族类别中的比例超过 15% 。 让我们对diversity_metric序列进行排序,以找出它们是哪些:
>>> diversity_metric.sort_values(ascending=False).head()
INSTNM
Regency Beauty Institute-Austin          5
Central Texas Beauty College-Temple      5
Sullivan and Cogliano Training Center    4
Ambria College of Nursing                4
Berkeley College-New York                4
dtype: int64
  1. 学校可以这么多样化似乎有点可疑。 让我们看一下这两家顶级学校的原始百分比。.loc索引器用于根据索引标签专门选择:
>>> college_ugds_.loc[['Regency Beauty Institute-Austin', 
                       'Central Texas Beauty College-Temple']]

  1. 似乎有几个类别被汇总到“未知”和“两个或多个种族”列中。 无论如何,它们似乎都非常不同。 我们可以看到美国新闻学院排名前 10 的学校在这一基本多样性指标方面的表现如何:
>>> us_news_top = ['Rutgers University-Newark',
                   'Andrews University', 
                   'Stanford University', 
                   'University of Houston',
                   'University of Nevada-Las Vegas']

>>> diversity_metric.loc[us_news_top]
INSTNM
Rutgers University-Newark         4
Andrews University                3
Stanford University               3
University of Houston             3
University of Nevada-Las Vegas    3
dtype: int64

工作原理

第 2 步进行计数,然后显示缺失值最多的学校。 由于数据帧中有九列,因此每所学校的缺失值最大数目为九。 许多学校缺少每一列的值。 步骤 3 删除所有值均缺失的行。 步骤 3 中的dropna方法具有how参数,该参数默认为字符串any,但也可以更改为all。 设置为any时,它将删除包含一个或多个缺失值的行。 设置为all时,它仅删除缺少所有值的行。

在这种情况下,我们保守地删除丢失所有值的行。 这是因为某些缺失值可能仅代表 0% 。 这不是碰巧的情况,因为执行dropna之后没有丢失值。 如果仍然缺少值,我们可以运行fillna(0)方法用 0 填充所有剩余值。

步骤 4 使用大于或等于方法ge开始我们的多样性指标计算。 这将导致所有布尔值的数据帧,通过设置axis='columns'将其水平求和。

在第 5 步中使用value_counts方法来生成我们的多样性指标的分布。 对于学校而言,很少有三个种族的大学生人数占总人数的 15% 或更多。 第 7 步和第 8 步根据我们的指标找到最多样化的两所学校。 尽管它们是多种多样的,但似乎很多种族并没有得到充分考虑,并且被默认为未知类别和两个或多个类别。

步骤 9 从“美国新闻”文章中选择排名前五的学校。 然后,从我们新创建的序列中选择其多样性指标。 事实证明,这些学校在我们的简单排名系统中也得分很高。

更多

另外,我们可以通过按最大种族百分比对它们进行排序来找到差异最小的学校:

>>> college_ugds_.max(axis=1).sort_values(ascending=False).head(10)
INSTNM
Dewey University-Manati                               1.0
Yeshiva and Kollel Harbotzas Torah                    1.0
Mr Leon's School of Hair Design-Lewiston              1.0
Dewey University-Bayamon                              1.0
Shepherds Theological Seminary                        1.0
Yeshiva Gedolah Kesser Torah                          1.0
Monteclaro Escuela de Hoteleria y Artes Culinarias    1.0
Yeshiva Shaar Hatorah                                 1.0
Bais Medrash Elyon                                    1.0
Yeshiva of Nitra Rabbinical College                   1.0
dtype: float64

我们还可以确定是否有任何一所学校的所有 9 个种族类别都超过 1% :

>>> (college_ugds_ > .01).all(axis=1).any()
True

另见

三、开始数据分析

在本章中,我们将介绍以下主题:

  • 制定数据分析计划
  • 通过更改数据类型减少内存
  • 从最大值中选择最小值
  • 通过排序选择每个组中最大的组
  • sort_values替代nlargest
  • 计算追踪止损单价格

介绍

重要的是,要考虑作为分析人员在将数据集作为数据帧导入工作区后首次遇到数据集时应采取的步骤。 您通常会首先执行一组任务来检查数据吗? 您是否了解所有可能的数据类型? 本章首先介绍您第一次遇到新的数据集时可能要执行的任务。 本章通过回答在 Pandas 中不常见的常见问题继续进行。

制定数据分析计划

尽管开始数据分析时没有标准方法,但是通常最好在首次检查数据集时为自己开发一个例程。 类似于我们用于起床,洗澡,上班,吃饭等的常规例程,开始的数据分析例程可帮助人们快速熟悉新的数据集。 该例程可以表现为动态任务清单,随着您对 Pandas 的熟悉和数据分析的扩展而不断发展。

探索性数据分析EDA)是一个术语,用于涵盖数据分析的整个过程,而无需正式使用统计测试程序。 EDA 的许多工作都涉及可视地显示数据之间的不同关系,以检测有趣的模式并提出假设。

准备

本秘籍涵盖了 EDA 的一小部分但又是基础部分:以常规方式和系统方式收集元数据单变量描述性统计信息。 它概述了在首次将任何数据集作为 pandas 数据帧导入时可以执行的一组常见任务。 此秘籍可能有助于形成您在首次检查数据集时可以实现的例程的基础。

元数据描述数据集,或更恰当地描述关于数据的数据。 元数据的示例包括列/行数,列名称,每列的数据类型,数据集的来源,收集日期,不同列的可接受值,等等。 单变量描述性统计信息是有关数据集的各个变量(列)的摘要统计信息,独立于所有其他变量。

操作步骤

首先,将收集college数据集上的一些元数据,然后是每列的基本摘要统计信息:

  1. 读取数据集,并使用head方法查看前五行:
>>> college = pd.read_college('data/college.csv')
>>> college.head()

  1. 使用shape属性获取数据帧的尺寸:
>>> college.shape
>>> (7535, 27)
  1. 使用info方法列出每一列的数据类型,非缺失值的数量以及内存使用情况:
>>> college.info()

  1. 获取数字列的摘要统计信息,并转置数据帧以获得更可读的输出:
>>> college.describe(include=[np.number]).T

  1. 获取对象和分类列的摘要统计信息:
>>> college.describe(include=[np.object, pd.Categorical]).T

工作原理

导入数据集后,常见的任务是打印出数据帧的前几行,以使用head方法进行手动检查。shape属性返回第一条元数据,即包含行数和列数的元组。

一次获取最多元数据的主要方法是info方法。 它提供每个列的名称,非缺失值的数量,每个列的数据类型以及数据帧的近似内存使用情况。 对于所有数据帧,列值始终是一种数据类型。 关系数据库也是如此。 总体而言,数据帧可能由具有不同数据类型的列组成。

在内部,Pandas 将相同数据类型的列一起存储在块中。 要深入了解 Pandas 的内部,请参阅 Jeff Tratner 的幻灯片

步骤 4 和步骤 5 在不同类型的列上生成单变量描述性统计信息。 强大的describe方法根据提供给include参数的数据类型产生不同的输出。 默认情况下,describe输出所有数字(主要是连续)列的摘要,并静默删除任何类别列。 您可以使用np.number或字符串number在摘要中包含整数和浮点数。 从技术上讲,数据类型是层次结构的一部分,其中数字位于整数和浮点上方。 查看下图,以更好地了解 NumPy 数据类型层次结构:

一般来说,我们可以将数据分类为连续数据或分类数据。 连续数据始终是数字,通常可以具有无限多种可能性,例如身高,体重和薪水。 分类数据代表离散值,这些离散值具有有限的可能性,例如种族,就业状况和汽车颜色。 分类数据可以用数字或字符表示。

分类列通常将是np.objectpd.Categorical类型。 步骤 5 确保同时代表这两种类型。 在第 4 步和第 5 步中,输出数据帧均带有T属性。 这简化了具有许多列的数据帧的可读性。

更多

当与数字列一起使用时,可以指定从describe方法返回的确切分位数:

>>> college.describe(include=[np.number], 
                     percentiles=[.01, .05, .10, .25, .5,
                                  .75, .9, .95, .99]).T

数据字典

数据分析的关键部分涉及创建和维护数据字典。 数据字典是元数据表和每列数据上的注释。 数据字典的主要目的之一是解释列名的含义。 高校数据集使用许多缩写,这对于首次检查它的分析师而言可能是陌生的。

以下college_data_dictionary.csv文件中提供了大学数据集的数据字典:

>>> pd.read_csv('data/collge_data_dictionaray.csv')

如您所见,它在解密缩写列名称方面非常有用。 实际上,数据帧不是存储数据字典的最佳位置。 诸如 Excel 或 Google 表格之类的平台具有易于编辑值和附加列的能力,是更好的选择。 至少,应在数据字典中包含一列以跟踪数据注释。 数据字典是您作为协作者的分析师可以共享的第一件事。

通常,您正在使用的数据集源自数据库,您必须联系该数据库的管理员才能获取更多信息。 正式的电子数据库通常具有更正式的数据表示形式,称为模式。 如果可能,请尝试与对设计有专业知识的人员一起调查您的数据集。

另见

通过更改数据类型减少内存

Pandas 并未将数据大致分为连续数据或分类数据,但对许多不同的数据类型都有精确的技术定义。

准备

此秘籍将大学数据集中的对象列之一的数据类型更改为特殊的 Pandas 分类数据类型,以大大减少其内存使用量。

操作步骤

  1. 阅读我们的大学数据集后,我们选择几列不同的数据类型,这些列将清楚地显示可以节省多少内存:
>>> college = pd.read_csv('data/college.csv')
>>> different_cols = ['RELAFFIL', 'SATMTMID', 'CURROPER',
 'INSTNM', 'STABBR']
>>> col2 = college.loc[:, different_cols]
>>> col2.head()

  1. 检查每一列的数据类型:
>>> col2.dtypes
RELAFFIL      int64
SATMTMID    float64
CURROPER      int64
INSTNM       object
STABBR       object
dtype: object
  1. 使用memory_usage方法查找每一列的内存使用情况:
>>> original_mem = col2.memory_usage(deep=True)
>>> original_mem
Index           80
RELAFFIL     60280
SATMTMID     60280
CURROPER     60280
INSTNM      660240
STABBR      444565
dtype: int64
  1. RELAFFIL列不需要使用 64 位,因为它仅包含 0/1 值。 让我们使用astype方法将此列转换为 8 位(1 字节)整数:
>>> col2['RELAFFIL'] = col2['RELAFFIL'].astype(np.int8)
  1. 使用dtypes属性来确认数据类型更改:
>>> col2.dtypes
RELAFFIL       int8
SATMTMID    float64
CURROPER      int64
INSTNM       object
STABBR       object
dtype: object
  1. 再次查找每一列的内存使用情况,并注意减少的地方:
>>> college[different_cols].memory_usage(deep=True)
Index           80 
RELAFFIL      7535 
SATMTMID     60280 
CURROPER     60280 
INSTNM      660240
STABBR      444565
  1. 为了节省更多内存,如果对象数据类型的基数相当低(唯一值数量),则将需要考虑将其更改为分类。 首先让我们检查两个对象列的唯一值数量:
>>> col2.select_dtypes(include=['object']).nunique()
INSTNM    7535
STABBR      59
dtype: int64
  1. STABBR列是转换为分类的很好的候选者,因为其值的唯一值少于百分之一:
>>> col2['STABBR'] = col2['STABBR'].astype('category')
>>> col2.dtypes
RELAFFIL        int8
SATMTMID     float64
CURROPER       int64
INSTNM        object
STABBR      category
dtype: object
  1. 再次计算内存使用情况:
>>> new_mem = col2.memory_usage(deep=True)
>>> new_mem
Index           80
RELAFFIL      7535
SATMTMID     60280
CURROPER     60280
INSTNM      660699
STABBR       13576
dtype: int64
  1. 最后,让我们比较原始内存使用情况和更新后的内存使用情况。 正如预期的那样,RELAFFIL列是其原始大小的八分之一,而STABBR列已缩小到其原始大小的百分之三:
>>> new_mem / original_mem
Index       1.000000
RELAFFIL    0.125000
SATMTMID    1.000000
CURROPER    1.000000
INSTNM      1.000695
STABBR      0.030538
dtype: float64

工作原理

Pandas 将integerfloat数据类型默认为 64 位,而不管特定数据帧的最大必要大小如何。 可以使用astype方法将整数,浮点数甚至是布尔值强制转换为其他数据类型,并将其作为字符串或特定对象的确切类型传递给它,如步骤 4 所示。

RELAFFIL列是转换为较小整数类型的好选择,因为数据字典说明其值必须为 0/1。 现在RELAFFIL的内存是CURROPER的八分之一,仍然是以前的类型。

显示的存储单位是字节而不是位。 1 个字节等于 8 位,因此当将RELAFFIL更改为 8 位整数时,它将使用 1 个 1 字节的内存,并且由于有 7,535 行,因此其内存占用量相当于 7,535 个字节。

对象数据类型的列(例如INSTNM)与其他 pandas 数据类型不同。 对于所有其他 Pandas 数据类型,该列中的每个值都是相同的数据类型。 例如,当列具有int64类型时,每个单独的列值也都是int64。 对于对象数据类型的列,情况并非如此。 每个单独的列值可以是任何类型。 对象数据类型可以混合使用字符串,数字,日期时间,甚至其他 Python 对象(例如列表或元组)。 因此,对于与任何其他数据类型都不匹配的数据列,有时将对象数据类型称为全部捕获。 但是,绝大多数时候,对象数据类型列都是字符串。

关系数据库管理系统(例如微软的 SQL Server 或 PostgreSQL)具有用于字符的特定数据类型,例如varchartextnchar,它们通常也指定最大字符数。 Pandas 对象数据类型是更广泛的数据类型。 对象列中的每个值可以是任何数据类型。

因此,对象数据类型列中每个单独值的存储都不一致。 像其他数据类型一样,每个值都没有预定义的内存量。 为了使 Pandas 提取对象数据类型列的确切内存量,必须在memory_usage方法中将deep参数设置为True

对象列是最大节省内存的目标。 Pandas 还有 NumPy 中不提供的其他分类数据类型。 当转换为category时,Pandas 内部会创建从整数到每个唯一字符串值的映射。 因此,每个字符串仅需要在内存中保留一次。 如您所见,这种简单的数据类型更改将内存使用量减少了 97% 。

您可能还已经注意到,索引使用的内存量极低。 如果在创建数据帧的过程中未指定索引(如本秘籍所述),pandas 会将索引默认为RangeIndexRangeIndex与内置范围函数非常相似。 它按需产生值,并且仅存储创建索引所需的最少信息量。

更多

为了更好地了解对象数据类型的列与整数和浮点数之间的区别,可以修改这些列中每个列的单个值,并显示结果的内存使用情况。CURROPERINSTNM列分别为int64和对象类型,:

>>> college.loc[0, 'CURROPER'] = 10000000
>>> college.loc[0, 'INSTNM'] = college.loc[0, 'INSTNM'] + 'a'
>>> college[['CURROPER', 'INSTNM']].memory_usage(deep=True)
Index           80
CURROPER     60280
INSTNM      660345

CURROPER的内存使用量保持不变,因为 64 位整数足以容纳更大的数字。 另一方面,仅将一个字母添加到一个值中,INSTNM的内存使用量增加了 105 个字节。

Python 3 使用 Unicode,这是一种标准的字符表示形式,旨在对世界上所有的书写系统进行编码。 Unicode 每个字符最多使用 4 个字节。 第一次对字符值进行修改时,Pandas 似乎有一些开销(100 字节)。 之后,每个字符增加 5 个字节。

并非所有列都可以强制转换为所需的类型。 看一下MENONLY列,在数据字典中似乎只包含 0/1 值。 导入时该列的实际数据类型意外地为float64。 这样做的原因是碰巧缺少值,用np.nan表示。 没有整数表示丢失的值。 甚至只有一个缺失值的任何数字列都必须是浮点数。 此外,如果其中一个值丢失,则integer数据类型的任何列将自动强制为浮点型:

>>> college['MENONLY'].dtype
dtype('float64')

>>> college['MENONLY'].astype(np.int8)
ValueError: Cannot convert non-finite values (NA or inf) to integer

此外,在引用数据类型时,可以用字符串名称代替 Python 对象。 例如,当在describe数据帧方法中使用include参数时,可以传递形式对象 NumPy / pandas 对象或其等效字符串表示形式的列表。 这些内容可在第 2 章,“基本数据帧操作”中的“用方法选择列”秘籍的开头的表格中找到。 例如,以下每个产生相同的结果:

>>> college.describe(include=['int64', 'float64']).T
>>> college.describe(include=[np.int64, np.float64]).T
>>> college.describe(include=['int', 'float']).T 
>>> college.describe(include=['number']).T      

在更改类型时,可以类似地使用以下字符串:

>>> college['MENONLY'] = college['MENONLY'].astype('float16')
>>> college['RELAFFIL'] = college['RELAFFIL'].astype('int8')

字符串与 Pandas 或 NumPy 纯对象的等价性出现在 Pandas 库中的其他位置,并且可能导致混乱,因为有访问同一事物的两种不同的方式。

最后,可以看到最小的RangeIndexInt64Index之间存在巨大的存储差异,后者将每个行索引存储在内存中:

>>> college.index = pd.Int64Index(college.index)
>>> college.index.memory_usage() # previously was just 80
60280 

另见

从最大值中选择最小值

此秘籍可用于创建吸引人的新闻头条,例如“在前 100 名最好的大学中,这 5 名学费最低”或“在前 50 个城市中,这 10 个是最便宜的”。 在分析期间,可能首先需要找到一个数据组,该数据组在单个列中包含最高的n值,然后从该子集中找到最低的m基于不同列的值。

准备

在本秘籍中,我们利用便利的方法nlargestnsmallest从前 100 名得分最高的电影中找到了前五部预算最低的电影。

操作步骤

  1. 读取电影数据集,然后选择movie_titleimdb_scorebudget列:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie2 = movie[['movie_title', 'imdb_score', 'budget']]
>>> movie2.head()

  1. 使用nlargest方法通过imdb_score选择前 100 个电影:
>>> movie2.nlargest(100, 'imdb_score').head()

  1. 链接nsmallest方法可返回前 100 名得分最低的五部预算电影:
>>> movie2.nlargest(100, 'imdb_score').nsmallest(5, 'budget')

工作原理

nlargest方法的第一个参数n必须为整数,并选择要返回的行数。 第二个参数columns以列名作为字符串。 步骤 2 返回得分最高的 100 部电影。 我们可以将该中间结果另存为自己的变量,但是,我们在步骤 3 中将nsmallest方法链接到该变量,该方法恰好返回五行,按budget排序。

更多

可以将列名列表传递给nlargest/nsmallest方法的columns参数。 仅当在列表的第一列中存在重复的值共享第 n 个排名位的情况时,这才对打破关系有用。

通过排序选择每个组中的最大值

在数据分析期间执行的最基本,最常见的操作之一是选择包含组中某个列的最大值的行。 例如,这就像在内容分级中查找每年评分最高的电影或票房最高的电影。 要完成此任务,我们需要对组以及用于对组中每个成员进行排名的列进行排序,然后提取每个组的最高成员。

准备

在此秘籍中,我们将找到每年评分最高的电影。

操作步骤

  1. 读入电影数据集并将其缩小为我们关心的三列movie_titletitle_yearimdb_score
>>> movie = pd.read_csv('data/movie.csv')
>>> movie2 = movie[['movie_title', 'title_year', 'imdb_score']]
  1. 使用sort_values方法按title_year对数据帧进行排序。 默认行为从最小到最大。 通过将ascending参数设置为等于True,可以反转此行为:
>>> movie2.sort_values('title_year', ascending=False).head()

  1. 注意只有年份是如何排序的。 要一次对多列进行排序,请使用一个列表。 让我们看看如何对年份和分数进行排序:
>>> movie3 = movie2.sort_values(['title_year','imdb_score'],
                                 ascending=False)
>>> movie3.head()

  1. 现在,我们使用drop_duplicates方法仅保留每年的第一行:
>>> movie_top_year = movie3.drop_duplicates(subset='title_year')
>>> movie_top_year.head()

工作原理

在第 1 步中,我们将数据集精简为仅关注重要的列。 此秘籍将与整个数据帧相同。 第 2 步显示了如何按单个列对数据帧进行排序,这并不是我们想要的。 步骤 3 同时对多个列进行排序。 它首先通过对所有title_year排序,然后在title_year的每个不同值内按imdb_score排序来工作。

drop_duplicates方法的默认行为是保留每个唯一行的第一次出现,因为每一行都是唯一的,所以不会删除任何行。 但是,subset参数将其更改为仅考虑为其提供的列(或列列表)。 在此示例中,每年仅返回一行。 正如我们在最后一步中按年份和得分排序一样,我们获得的年度最高评分电影。

更多

可以按升序对一列进行排序,而同时按降序对另一列进行排序。 为此,请将布尔值列表传递给ascending参数,该参数与您希望对每一列进行排序的方式相对应。 以下title_yearcontent_rating降序排列,budget升序排列。 然后,它查找每年和内容分级组中预算最低的电影:

>>> movie4 = movie[['movie_title', 'title_year',
                    'content_rating', 'budget']]
>>> movie4_sorted = movie4.sort_values(['title_year', 
                                        'content_rating', 'budget'], 
                                        ascending=[False, False, True])
>>> movie4_sorted.drop_duplicates(subset=['title_year', 
                                          'content_rating']).head(10)

默认情况下,drop_duplicates保持最开始的外观,但是可以通过在最后传递keep参数来选择每个组的最后一行,或通过False完全删除所有重复项来修改此行为

sort_values替代nlargest

前两个秘籍的工作原理类似,它们以略有不同的方式对值进行排序。 查找一列数据的顶部n值等同于对整个列进行降序排序并获取第一个n值。 Pandas 有许多可以通过多种方式做到这一点的行动。

准备

在本秘籍中,我们将使用sort_values方法复制“从最大值中选择最小值”秘籍,并探讨两者之间的区别。

操作步骤

  1. 让我们从“从最大值中选择最小值”秘籍的最后步骤中重新创建结果:
>>> movie = pd.read_csv('data/movie.csv')
>>> movie2 = movie[['movie_title', 'imdb_score', 'budget']]
>>> movie_smallest_largest = movie2.nlargest(100, 'imdb_score') \
                                   .nsmallest(5, 'budget')
>>> movie_smallest_largest

  1. 使用sort_values复制表达式的第一部分,并使用head方法获取第一100行:
>>> movie2.sort_values('imdb_score', ascending=False).head(100)
  1. 现在,我们拥有得分最高的 100 部电影,我们可以再次将sort_valueshead结合使用,以budget来获得最低的五部电影:
>>> movie2.sort_values('imdb_score', ascending=False).head(100) \
          .sort_values('budget').head()

工作原理

如步骤 2 所示,通过在操作后链接head方法,sort_values方法几乎可以复制nlargest。步骤 3 通过链接另一个sort_values可以复制nsmallest,并且只需取前五个即可完成查询。head方法显示行。

查看步骤 1 中第一个数据帧的输出,并将其与步骤 3 中的输出进行比较。它们是否相同? 没有! 发生了什么? 要了解为什么两个结果不相等,让我们看一下每个秘籍的中间步骤的尾部:

>>> movie2.nlargest(100, 'imdb_score').tail()

>>> movie2.sort_values('imdb_score', ascending=False) \
          .head(100).tail()

由于存在超过 100 部评分至少为8.4的电影而引起问题。 每种方法nlargestsort_values的联系均不同,导致 100 行数据帧略有不同。

更多

如果查看nlargest文档,则会看到keep参数具有三个可能的值,firstlastFalse。 据我对其他 Pandas 方法的了解,keep=False应该允许所有纽带保留在结果中。 不幸的是,Pandas 在尝试执行此操作时会引发错误。 我在 GitHub 上给 Pandas 开发团队创建了一个问题,以进行此改进。

计算追踪止损单的价格

本质上,有无数种交易股票的策略。 许多投资者采用的一种基本交易类型是止损单。 止损单是投资者下达的买卖股票的命令,每当市场价格达到某个点时,该订单就会执行。 止损单对于防止巨大损失和保护收益都是有用的。

就本秘籍而言,我们将仅检查用于出售当前拥有股票的止损单。 在典型的止损订单中,价格在订单的整个生命周期内都不会改变。 例如,如果您以每股 100 美元的价格购买了股票,则可能希望将停止订单设置为每股 90 美元,以将下行空间限制为 10% 。

一种更高级的策略是,如果价值增加,则不断修改止损单的销售价格以跟踪股票的价值。 这称为追踪止损指令。 具体来说,如果相同的 100 美元股票增加到 120 美元,那么低于当前市场价格 10% 的追踪止损单将使销售价格上涨到 108 美元。

自购买之日起,追踪止损单永远不会向下移动,并始终与最大值挂钩。 如果股票价格从 120 美元跌至 110 美元,止损单仍将保持在 108 美元。 仅当价格升至 120 美元以上时,价格才会增加。

准备

此秘籍需要使用第三方包pandas-datareader来在线获取股市价格。 它没有预装在 Anaconda 发行版中。 要安装此包,只需访问命令行并运行conda install pandas-datareader。 如果没有 Anaconda,可以通过运行pip install pandas-datareader进行安装。 该秘籍确定给定任何股票的初始购买价格的追踪止损单价格。

操作步骤

  1. 首先,我们将与 Tesla Motors(TSLA)股票合作,并假设在 2017 年的第一个交易日进行购买:
>>> import pandas_datareader as pdr
>>> tsla = pdr.DataReader('tsla', data_source='google',
                          start='2017-1-1')
>>> tsla.head(8)

  1. 为简单起见,我们将使用每个交易日的收盘价:
>>> tsla_close = tsla['Close']
  1. 使用cummax方法跟踪直到当前日期的最高收盘价:
>>> tsla_cummax = tsla_close.cummax()
>>> tsla_cummax.head(8)
Date
2017-01-03    216.99
2017-01-04    226.99
2017-01-05    226.99
2017-01-06    229.01
2017-01-09    231.28
2017-01-10    231.28
2017-01-11    231.28
2017-01-12    231.28
Name: Close, dtype: float64
  1. 为了将下行限制为 10% ,我们将tsla_cummax乘以 0.9。 这将创建跟踪止损单:
>>> tsla_trailing_stop = tsla_cummax * .9
>>> tsla_trailing_stop.head(8)
Date
2017-01-03    195.291
2017-01-04    204.291
2017-01-05    204.291
2017-01-06    206.109
2017-01-09    208.152
2017-01-10    208.152
2017-01-11    208.152
2017-01-12    208.152
Name: Close, dtype: float64

工作原理

cummax方法通过保留遇到的最大值直到并包括当前值来工作。 将该序列乘以 0.9 或您要使用的任何缓冲,将创建跟踪止损单。 在此特定示例中,TSLA 的值增加了,因此其尾随止损也增加了。

更多

该秘籍仅介绍了如何使用有用的 Pandas 来交易证券,并且在计算止损单是否触发以及何时触发止损时停止了计算。 可以将此秘籍转换为接受代码,购买日期和止损百分比并返回尾随止损价格的函数:

>>> def set_trailing_loss(symbol, purchase_date, perc):
        close = pdr.DataReader(symbol, 'google',
                               start=purchase_date)['Close']
        return close.cummax() * perc

>>> msft_trailing_stop = set_trailing_loss('msft', '2017-6-1', .85)
>>> msft_trailing_stop.head()
Date
2017-06-01    59.585
2017-06-02    60.996
2017-06-05    61.438
2017-06-06    61.642
2017-06-07    61.642
Name: Close, dtype: float64

在减肥计划中可以使用非常相似的策略。 只要您偏离最小体重太远,都可以设置警告。 Pandas 为您提供了cummin方法来跟踪最小值。 如果您连续跟踪每天的体重,则以下代码可提供比迄今为止最低记录体重高出 5% 的尾随减肥:

>>> weight.cummin() * 1.05

另见

  • Pandas 的另外两种累积方法的官方文档,cumsumcumprod