精通-Pandas-全-

精通 Pandas(全)

原文:Mastering Pandas

协议:CC BY-NC-SA 4.0

零、前言

欢迎来到《精通 Pandas》。 本书将教您如何有效使用 pandas,pandas 是当今用于执行数据分析的最受欢迎的 Python 包之一。 本书的前半部分从进行数据分析的原理开始。 然后,它特别介绍了 Python 和 pandas,引导您完成安装步骤,pandas 的全部内容,pandas 的用途,pandas 中的数据结构以及如何在 pandas 中选择,合并和分组数据。 然后介绍处理缺失的数据和时间序列数据,以及为数据可视化作图。

本书的下半部分将向您展示如何使用 pandas 通过经典方法和贝叶斯方法进行推理统计,随后是有关 pandas 架构的一章,然后以机器学习的旋风为结尾,其中介绍了 scikit-learn 库。 本书的目的是通过使用真实数据集上的说明性示例,使您沉迷于 Pandas。

本书涵盖的内容

第 1 章,“Pandas 和数据分析简介”解释了进行数据分析的动机,介绍了 Python 语言和 Pandas 库,并讨论了如何将它们用于数据分析。 它还描述了使用 Pandas 进行数据分析的好处。

第 2 章,“Pandas 的安装和支持软件”给出了如何安装 Pandas 的详细说明。 它提供了跨多个操作系统平台的安装说明:Unix,MacOSX 和 Windows。 它还描述了如何安装支持软件,例如 NumPy 和 IPython。

第 3 章,“Pandas 数据结构”介绍了构成 Pandas 库基础的数据结构。 首先介绍并讨论了numpy.ndarray数据结构,因为它构成了 Pandas 的基础。序列和pandas.DataFrame数据结构是 Pandas 所使用的基础数据结构。 本章可能是本书中最重要的章节,因为对这些数据结构的知识对于使用 Pandas 进行数据分析是绝对必要的。

第 4 章,“Pandas 的操作,第 I 部分 – 索引和选择”,着重介绍如何从 Pandas 数据结构访问和选择数据。 它讨论了通过“基本”,“标签”,“整数”和“混合索引”选择数据的各种方式。 它解释了更高级的索引概念,例如多重索引,布尔索引和对索引类型的操作。

第 5 章,“Pandas 的操作,第二部分 -- 数据的分组,合并和重塑”解决了在 Pandas 的数据结构中重新排列数据的问题。 通过在实际数据集上利用 Pandas 中允许用户重新排列数据的各种功能,我们对其进行了研究。 本章研究了可以重新排列数据的不同方式:通过聚合/分组,合并,连接和重塑。

第 6 章,“缺失数据,时间序列和 Matplotlib 绘图”,讨论了预处理数据所需的主题,这些数据将用作数据分析,预测的输入和可视化。 这些主题包括如何处理输入数据中的缺失值,如何处理时间序列数据以及如何使用 matplotlib 库为可视化目的绘制数据。

第 7 章,“统计学之旅 -- 经典方法”向您简要介绍经典统计学,并展示如何将 Pandas 与 Python 的统计包一起使用以进行统计分析。 涉及各种统计主题,包括统计推断,中心趋势的度量,假设检验,Z 检验和 T 检验,方差分析,置信区间以及相关性和回归。

第 8 章,“贝叶斯统计简介”讨论了执行统计分析的另一种方法,称为贝叶斯分析。 本章介绍贝叶斯统计,并讨论基本的数学框架。 它检查了贝叶斯分析中使用的各种概率分布,并展示了如何使用 matplotlib 和scipy.stats生成和可视化它们。 它还介绍了用于执行蒙特卡洛模拟的 PyMC 库,并提供了使用在线数据进行贝叶斯推理的真实示例。

第 9 章,“Pandas 库架构”提供了有关 Pandas 基本代码的相当详细的描述。 它详细介绍了 Pandas 库代码的组织方式,并描述了组成 Pandas 的各种模块,并提供了一些详细信息。 它还有一部分向用户展示如何使用扩展来改善 Python 和 Pandas 的性能。

第 10 章,“R 和 Pandas 比较”着重比较 Pandas 与 R,这是许多 Pandas 功能的基础统计包。 本章比较了 R 数据类型及其对应的 pandas,并说明了两种库中各种操作的比较方式。 比较了切片,选择,算术运算,聚合,分组,匹配,拆分应用合并和熔化等操作。

第 11 章,“机器学习简介”带领您进行机器学习的旋风之旅,重点是使用 pandas 库作为将输入数据预处理为机器学习程序的工具。 它还介绍了 scikit-learn 库,它是 Python 中使用最广泛的机器学习工具包。 通过将各种机器学习技术和算法应用于一个著名的机器学习分类问题:哪些乘客在泰坦尼克号沉没中幸存了下来?

这本书需要什么

该软件适用于本书的所有章节:

  • Windows/MacOS/Linux
  • Python 2.7.x
  • Pandas
  • IPython
  • R
  • scikit-learn

对于硬件,没有特殊要求,因为 Python 和 pandas 可以在装有 Mac,Linux 或 Windows 的任何 PC 上运行。

这本书是给谁的

本书适用于已经对 Python 有基本了解并希望深入了解其数据分析功能的 Python 程序员,数学家和分析家。 也许在使用 Python 几个月后您的食欲已被削弱,或者您是 R 用户,但希望研究 Python 在数据分析方面所提供的功能。 无论哪种情况,这本书都会帮助您掌握 Pandas 的核心特征和功能,以进行数据分析。 对于用户来说,拥有一些使用 Python 的经验或使用诸如 R 的数据分析包的经验会很有帮助。

约定

在本书中,您将找到许多可以区分不同类型信息的文本样式。 以下是这些样式的一些示例,并解释了其含义。

文本,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄中的代码字如下所示:“安装后,应将以下文件夹添加到PATH环境变量中 :C:\Python27\C:\Python27\Tools\Scripts。”

任何命令行输入或输出的编写方式如下:

brew install readline
brew install zeromq
pip install ipython pyzmq tornado pygments

新术语重要词以粗体显示。 您在屏幕上看到的字词,例如在菜单或对话框中,将像这样显示在文本中:“PYMC Pandas 示例的先前图像摘自这里。”

注意

警告或重要提示会出现在这样的框中。

提示

提示和技巧如下所示。

一、Pandas 和数据分析简介

在本章中,我们解决以下问题:

  • 数据分析的动机
  • 如何将 Python 和 Pandas 用于数据分析
  • Pandas 库的描述
  • 使用 Pandas 的好处

数据分析的动机

在本节中,我们将讨论使数据分析成为当今快速发展的技术环境中日益重要的工作领域的趋势。

我们生活在大数据世界中

在过去两年中,术语大数据已成为最热门的技术流行语之一。 现在,我们越来越多地在各种媒体上听到有关大数据的信息,并且大数据初创公司越来越多地吸引了风险投资。 零售领域的一个很好的例子是 Target Corporation,该公司已对大数据进行了大量投资,现在能够通过使用大数据来分析人们的在线购物习惯来识别潜在客户; 请参阅相关文章

松散地说,大数据是指这样一种现象,即数据量超过了数据接收者处理数据的能力。 这是一个有关大数据的维基百科条目,很好地总结了它

4V 大数据

开始思考大数据复杂性的一个好方法是沿着所谓的 4 维,即大数据的 4V。 该模型最初由 Gartner 分析师 Doug Laney 于 2001 年引入 3V。3V 代表容量,速度和种类,第四个 V 准确性后来被 IBM 添加。 Gartner 的正式定义如下:

“大数据是高容量,高速度和/或种类繁多的信息资产,需要新的处理形式以实现增强的决策,洞察力发现和过程优化。”

-- Laney,Douglas。 《大数据的重要性:定义》,Gartner

大数据的容量

大数据时代的数据量简直令人难以置信。 根据 IBM 的数据,到 2020 年,地球上的数据总量将激增至 40 ZB。 您听说正确的 40 ZB 为 43 万亿千兆字节,大约是4×10^21字节。 有关此的更多信息,请参阅 Zettabyte 上的维基百科页面

为了了解这将是多少数据,让我参考 2010 年发布的 EMC 新闻稿,其中指出 1 ZB 大约等于:

“地球上每个男人,女人和孩子连续‘鸣叫’ 100 年创造的数字信息”,或“750 亿个满载的 16 GB 苹果 iPad,将使温布利大球场的整个区域填满 41 次,勃朗峰隧道 84 次,欧洲核子研究组织的大型强子对撞机隧道 151 次,北京国家体育场 15.5 次或台北 101 塔 23 次……”

  • EMC 研究预测,到 2020 年数据将增长 45 倍

数据增长的速度很大程度上受以下几个因素的推动:

  • 互联网的快速增长。
  • 从模拟媒体到数字媒体的转换,以及增强的捕获和存储数据的能力,这又通过更便宜,更强大的存储技术得以实现。 诸如照相机和可穿戴设备之类的数字数据输入设备已经激增,并且巨大的数据存储成本迅速下降。 AWS 是价格便宜得多的趋势的一个典型例子。

设备的互联网化,或更确切地说是物联网,是一种常见的家用设备(例如我们的冰箱和汽车)将连接到互联网的现象。 这种现象只会加速上述趋势。

大数据的速度

从纯粹的技术角度来看,速度指的是大数据的吞吐量,即数据进入和处理的速度。 这对数据接收者需要多快的时间来处理数据以保持同步产生了影响。 实时分析是处理此特征的一种尝试。 可以帮助实现此目的的工具包括 AWS Elastic MapReduce。

在更宏的层面上,数据的速度也可以看作是提高了的速度,现在,数据和信息的传输和处理速度比以往任何时候都更快,更远。

高速数据和通信网络的激增,以及手机,平板电脑和其他连接设备的出现,是推动信息速度的主要因素。 速度的一些度量包括每秒的推文数量和每分钟的电子邮件数量。

大数据的种类

大数据的种类来自具有生成数据的多种数据源以及所生成数据的不同格式。

这给必须处理数据的数据接收者带来了技术挑战。 数码相机,传感器,网络,手机等都是一些生成不同格式数据的数据生成器,而挑战在于能够处理所有这些格式并从数据中提取有意义的信息。 随着大数据时代的到来,数据格式的不断变化的性质引发了数据库技术行业的一场革命,NoSQL 数据库的兴起可以处理所谓的非结构化数据格式,可互换或不断变化的数据。 有关 Couchbase 的更多信息,请参阅“为什么使用 NoSQL”

大数据的准确性

大数据的第四个特征 – 准确性(稍后添加)是指需要验证或确认数据的正确性或数据代表真相的事实。 必须验证数据源,并将错误保持在最低限度。 根据 IBM 的估计,糟糕的数据质量每年给美国经济造成 3.1 万亿美元的损失。 例如,2008 年,医疗错误给美国造成了 195 亿美元的损失。 有关更多信息,请参阅相关文章。 这是 IBM 的信息图,总结了大数据的 4V:

Veracity of big data

IBM 应对大数据的 4V

如此多的数据,很少的分析时间

谷歌前首席执行官埃里克·施密特(Eric Sc​​hmidt)将数据分析描述为万物的未来。 作为参考,您可以观看名为为什么数据分析是万物的未来的 YouTube 视频。

在大数据时代,数据的数量和速度将继续增加。 能够有效地收集,过滤和分析数据的公司所获得的信息将使他们能够在更短的时间内更好地满足客户的需求,这将获得比竞争对手更大的竞争优势。 例如,数据分析(度量文化)在 amazon 的业务策略中起着非常关键的作用。 有关更多信息,请参阅 Amazon 智能洞察的案例研究

迈向实时分析

随着技术和工具的发展,为了满足业务不断增长的需求,已经朝着所谓的实时分析迈进了一步。 有关英特尔“洞察一切”的更多信息,请访问这里

在大数据互联网时代,以下是一些示例:

  • 在线业务需要即时洞察力,以了解他们在在线市场中推出的新产品/功能的表现以及如何相应地调整其在线产品结构。 亚马逊就是一个很好的例子,他们的客户既查看了此商品,又查看了功能
  • 在金融领域,风险管理和交易系统几乎需要即时分析,以便根据数据驱动的见解做出有效的决策。

Python 和 Pandas 组合如何融入数据分析

Python 编程语言是当今新兴的数据科学和分析领域中增长最快的语言之一。 Python 是由 Guido von Russom 于 1991 年创建的,其主要功能包括:

  • 解释而不是编译
  • 动态类型系统
  • 通过对象引用传递值
  • 模块化能力
  • 综合库
  • 相对于其他语言的可扩展性
  • 面向对象
  • 大多数主要的编程范例都是过程式的,面向对象的,在较小程度上是函数式的。

注意

有关更多信息,请参见 Python 上的维基百科页面

使 Python 在数据科学中流行的特征包括其非常用户友好(人类可读)的语法,其被解释而不是编译的事实(导致更快的开发时间)以及其非常全面的用于分析和分析数据的库 ,以及其进行数值和统计计算的能力。 Python 的库提供了用于数据科学和分析的完整工具包。 主要内容如下:

  • NumPy :强调数值计算的通用数组功能
  • SciPy :数值计算
  • Matplotlib :图形
  • Pandas:序列和数据帧(一维和二维数组状类型)
  • Scikit-Learn :机器学习
  • NLTK :自然语言处理
  • Statstool :统计分析

在本书中,我们将重点关注上一个列表中列出的第 4 个库 Pandas。

什么是 Pandas?

pandas 是由 Wes McKinney 在 2008 年开发的用于 Python 数据分析的高性能开源库。多年来,它已成为使用 Python 进行数据分析的事实上的标准库。 该工具得到了广泛的采用,它背后的社区很大(到 03/2014 为止有 220 多个贡献者和 9000 多个提交),快速迭代,功能和不断增强。

Pandas 的一些主要特征包括:

  • 它可以处理不同格式的各种数据集:时间序列,表格异构数据和矩阵数据。
  • 它有助于从各种来源(例如 CSV 和 DB/SQL)加载/导入数据。
  • 它可以处理多种数据集操作:子集,切片,过滤,合并,分组,重新排序和重新整形。
  • 它可以根据用户/开发人员定义的规则处理缺失的数据:忽略,转换为 0,依此类推。
  • 它可以用于数据的解析和整理(转换)以及建模和统计分析。
  • 它与 statsmodels,SciPy 和 scikit-learn 等其他 Python 库很好地集成在一起。
  • 它提供了快速的性能,并且可以通过使用 Cython (Python 的 C 扩展)来进一步提高速度。

有关更多信息,请访问官方 Pandas 文档

使用 Pandas 的好处

Pandas 是 Python 数据分析语料库的核心组件。 Pandas 的显着特征是它提供的数据结构套件,自然适合于数据分析,主要是数据帧以及程度较小的序列(一维向量)和面板(3D 表)。

简而言之,pandas 和 statstools 可以描述为 Python 对 R 的回答,即数据分析和统计编程语言,它既提供数据结构(如 R 数据帧架),又提供丰富的统计库用于数据分析。

与使用 Java,C 或 C++ 之类的语言进行数据分析相比,Pandas 的好处是多方面的:

  • 数据表示:它可以通过其数据帧和序列数据结构以简洁的方式轻松地以自然适合于数据分析的形式表示数据。 在 Java/C/C++ 中进行等效操作需要许多行自定义代码,因为这些语言不是为数据分析而构建的,而是为网络和内核开发而构建的。
  • 数据子集和过滤:它提供了简单的数据子集和过滤,这些过程是进行数据分析的基础。
  • 简洁明了的代码:其简洁明了的 API 使用户可以更加专注于手头的核心目标,而不必编写大量的脚手架代码来执行日常任务。 例如,将 CSV 文件读取到内存中的数据帧数据结构中需要两行代码,而在 Java/C/C++ 中执行同一任务将需要更多的代码行或对非标准库的调用,如下表。 在这里,假设我们有以下数据:
国家 二氧化碳排放量 能量消耗 出生率 每千人的互联网使用量 预期寿命 人口
Belarus 2000 5.91 2988.71 1.29 18.69 68.01 1.00E+07
Belarus 2001 5.87 2996.81 43.15 9970260
Belarus 2002 6.03 2982.77 1.25 89.8 68.21 9925000
Belarus 2003 6.33 3039.1 1.25 162.76 9873968
Belarus 2004 3143.58 1.24 250.51 68.39 9824469
Belarus 2005 1.24 347.23 68.48 9775591

在 CSV 文件中,我们希望读取的数据如下所示:

Country,Year,CO2Emissions,PowerConsumption,FertilityRate,InternetUsagePer1000, LifeExpectancy, PopulationBelarus,2000,5.91,2988.71,1.29,18.69,68.01,1.00E+07Belarus,2001,5.87,2996.81,,43.15,,9970260Belarus,2002,6.03,2982.77,1.25,89.8,68.21,9925000...Philippines,2000,1.03,514.02,,20.33,69.53,7.58E+07Philippines,2001,0.99,535.18,,25.89,,7.72E+07Philippines,2002,0.99,539.74,3.5,44.47,70.19,7.87E+07...Morocco,2000,1.2,489.04,2.62,7.03,68.81,2.85E+07Morocco,2001,1.32,508.1,2.5,13.87,,2.88E+07Morocco,2002,1.32,526.4,2.5,23.99,69.48,2.92E+07..

此处的数据取自世界银行的经济数据

在 Java 中,我们必须编写以下代码:

public class CSVReader {
public static void main(String[] args) {
        String[] csvFile=args[1];CSVReader csvReader = new csvReader();List<Map>dataTable=csvReader.readCSV(csvFile);}public void readCSV(String[] csvFile){BufferedReader bReader=null;String line="";String delim=",";
  //Initialize List of maps, each representing a line of the csv fileList<Map> data=new ArrayList<Map>();
  try {bufferedReader = new BufferedReader(new   FileReader(csvFile));// Read the csv file, line by linewhile ((line = br.readLine()) != null){String[] row = line.split(delim);Map<String,String> csvRow=new HashMap<String,String>();
           csvRow.put('Country')=row[0]; csvRow.put('Year')=row[1];csvRow.put('CO2Emissions')=row[2]; csvRow.put('PowerConsumption')=row[3];csvRow.put('FertilityRate')=row[4];csvRow.put('InternetUsage')=row[1];csvRow.put('LifeExpectancy')=row[6];csvRow.put('Population')=row[7];data.add(csvRow);
        }
     } catch (FileNotFoundException e) {e.printStackTrace();
     } catch (IOException e) {e.printStackTrace();
    } 
 return data;}

但是,使用 Pandas,只需两行代码:

import pandas as pdworldBankDF=pd.read_csv('worldbank.csv')

此外,Pandas 基于 NumPy 库构建,因此继承了此包的许多性能优势,尤其是在数值和科学计算方面。 使用 Python 时常被吹捧的一个缺点是,作为一种脚本语言,它相对于 Java/C/C++ 等语言的性能一直很慢。 但是,Pandas 的情况并非如此。

总结

我们生活在以 4V 的容量,速度,多样性和准确性为特征的大数据时代。 在可预见的将来,数据的数量和速度一直在增长。 能够利用和分析大数据以提取信息并根据此信息做出可操作的决策的公司将成为市场的赢家。 Python 是一种快速发展的,用户友好的,可扩展的语言,在数据分析中非常流行。

pandas 是 Python 工具包的核心库,用于数据分析。 它提供的特性和功能比许多其他流行的语言(例如 Java,C,C++ 和 Ruby)使数据分析更加轻松和快捷。

因此,考虑到上一节中列出的 Python 的优势作为数据分析的一种选择,使用 Python 的数据分析从业人员应该变得对 Pandas 更为精通才能变得更加有效。 本书旨在帮助用户实现这一目标。

十、R 与 Pandas 的比较

本章着重于将 Pandas 与 R 进行比较,R 是对许多 Pandas 功能进行建模的统计包。 本手册旨在为希望使用 Pandas 的 R 用户以及希望复制他们在 Pandas R 代码中看到的功能的用户提供指南。 它着重介绍了 R 用户可用的一些关键功能,并通过使用一些说明性示例演示了如何在 Pandas 中实现类似功能。 本章假定您已安装 R 统计包。 如果没有,则可以从此处下载并安装

在本章的最后,数据分析用户应该比 Pandas 更好地掌握 R 的数据分析功能,从而使他们可以过渡到 Pandas 或使用 Pandas。 本章讨论的各个主题包括:

  • R 数据类型及其等效的 Pandas
  • 切片和选择
  • 数据类型列的算术运算
  • 聚合和分组
  • 匹配
  • 拆分合并
  • 熔化和重塑
  • 因子和类别数据

R 数据类型

R 具有五种原始或原子类型:

  • 字符
  • 数字
  • 整数
  • 复数
  • 逻辑/布尔

它还具有以下更复杂的容器类型:

  • 向量:类似于numpy.array。 它只能包含相同类型的对象。
  • 列表:这是一个异构容器。 相当于 Pandas 的序列。
  • 数据帧 :这是一个异构 2D 容器,等效于 Pandas 数据帧
  • 矩阵:它是向量的均质 2D 版本。 它类似于numpy.matrix

在本章中,我们将重点关注列表和数据帧,它们具有与序列和数据帧等效的 Pandas。

注意

有关 R 数据类型的更多信息,请参考这个文档

对于 NumPy 数据类型,请参考这个文档这个文档

R 列表

R 列表可以显式创建为列表声明,如下所示:

>h_lst<- list(23,'donkey',5.6,1+4i,TRUE)
>h_lst
[[1]]
[1] 23

[[2]]
[1] "donkey"

[[3]]
[1] 5.6

[[4]]
[1] 1+4i

[[5]]
[1] TRUE

>typeof(h_lst)
[1] "list"

以下是其在 Pandas 中的序列,包括创建列表和从中创建序列:

In [8]: h_list=[23, 'donkey', 5.6,1+4j, True]
In [9]: import pandas as pd
 h_ser=pd.Series(h_list)
In [10]: h_ser
Out[10]: 0        23
 1    donkey
 2       5.6
 3    (1+4j)
 4      True
dtype: object

Pandas 数组索引从 0 开始,而 R 则以 1 开始。下面是一个示例:

In [11]: type(h_ser)
Out[11]: pandas.core.series.Series

R 数据帧

我们可以通过调用data.frame()构造器来构造 R 数据帧,如下所示:

>stocks_table<- data.frame(Symbol=c('GOOG','AMZN','FB','AAPL',
 'TWTR','NFLX','LINKD'), 
 Price=c(518.7,307.82,74.9,109.7,37.1,
 334.48,219.9),
MarketCap=c(352.8,142.29,216.98,643.55,23.54,20.15,27.31))

>stocks_table
Symbol  PriceMarketCap
1   GOOG 518.70    352.80
2   AMZN 307.82    142.29
3     FB  74.90    216.98
4   AAPL 109.70    643.55
5   TWTR  37.10     23.54
6   NFLX 334.48     20.15
7  LINKD 219.90     27.31

在这里,我们构造一个 Pandas 数据帧并显示它:

In [29]: stocks_df=pd.DataFrame({'Symbol':['GOOG','AMZN','FB','AAPL', 
 'TWTR','NFLX','LNKD'],
 'Price':[518.7,307.82,74.9,109.7,37.1,
 334.48,219.9],
'MarketCap($B)' : [352.8,142.29,216.98,643.55,
 23.54,20.15,27.31]
 })
stocks_df=stocks_df.reindex_axis(sorted(stocks_df.columns,reverse=True),axis=1)
stocks_df
Out[29]:
Symbol  PriceMarketCap($B)
0       GOOG    518.70  352.80
1       AMZN    307.82  142.29
2       FB      74.90   216.98
3       AAPL    109.70  643.55
4       TWTR    37.10   23.54
5       NFLX    334.48  20.15
6       LNKD219.90  27.31

切片和选择

在 R 中,我们通过以下三种方式对对象进行切片:

  • [:这始终返回与原始对象相同类型的对象,并且可以用于选择多个元素。
  • [[:用于提取列表或数据帧的元素; 并且只能用于提取单个元素:返回的元素的类型不一定是列表或数据帧。
  • $:用于按名称提取列表或数据帧的元素,类似于[[

这是 R 中的一些切片示例及其在 Pandas 中的等效示例:

R 矩阵和 NumPy 数组比较

让我们看看 R 中的矩阵创建和选择:

>r_mat<- matrix(2:13,4,3)
>r_mat
 [,1] [,2] [,3]
[1,]    2    6   10
[2,]    3    7   11
[3,]    4    8   12
[4,]    5    9   13

要选择第一行,我们编写:

>r_mat[1,]
[1]  2  6 10

要选择第二列,我们使用以下命令:

>r_mat[,2]
[1] 6 7 8 9

现在让我们来看一下 NumPy 数组的创建和选择:

In [60]: a=np.array(range(2,6))
 b=np.array(range(6,10))
 c=np.array(range(10,14))
In [66]: np_ar=np.column_stack([a,b,c])
np_ar
Out[66]: array([[ 2,  6, 10],
[ 3,  7, 11],
[ 4,  8, 12],
[ 5,  9, 13]])

要选择第一行,请编写以下命令:

In [79]: np_ar[0,]
Out[79]: array([ 2,  6, 10])

索引在 R 和 pandas/NumPy 中是不同的。

在 R 中,索引从 1 开始,而在 pandas/NumPy 中,索引从 0 开始。因此,当从 R 转换到 pandas/NumPy 时,我们必须从所有索引中减去 1。

要选择第二列,请编写以下命令:

In [81]: np_ar[:,1]
Out[81]: array([6, 7, 8, 9])

另一个选择是先转置数组,然后选择列,如下所示:

In [80]: np_ar.T[1,]
Out[80]: array([6, 7, 8, 9])

R 列表和 Pandas 序列比较

这是在 R 中创建和选择列表的示例:

>cal_lst<- list(weekdays=1:8, mth='jan')
>cal_lst
$weekdays
[1] 1 2 3 4 5 6 7 8

$mth
[1] "jan"

>cal_lst[1]
$weekdays
[1] 1 2 3 4 5 6 7 8

>cal_lst[[1]]
[1] 1 2 3 4 5 6 7 8

>cal_lst[2]
$mth
[1] "jan"

Pandas 的序列创建和选择如下:

In [92]: cal_df= pd.Series({'weekdays':range(1,8), 'mth':'jan'})
In [93]: cal_df
Out[93]: mthjan
weekdays    [1, 2, 3, 4, 5, 6, 7]
dtype: object

In [97]: cal_df[0]
Out[97]: 'jan'

In [95]: cal_df[1]
Out[95]: [1, 2, 3, 4, 5, 6, 7]

In [96]: cal_df[[1]]
Out[96]: weekdays    [1, 2, 3, 4, 5, 6, 7]
dtype: object

在这里,从[][[]]运算符的角度来看,我们看到 R 列表和 Pandas 序列之间的差异。 我们可以通过考虑第二项(字符串)来看到差异。

对于 R,[]运算符产生一个容器类型,即包含字符串的列表,而[[]]产生一个原子类型:在这种情况下,一个字符如下:

>typeof(cal_lst[2])
[1] "list"
>typeof(cal_lst[[2]])
[1] "character"

对于 Pandas,情况恰恰相反:[]产生原子类型,而[[]]产生复杂类型,即序列的序列,如下所示:

In [99]: type(cal_df[0])
Out[99]: str

In [101]: type(cal_df[[0]])
Out[101]: pandas.core.series.Series

在 R 和 pandas 中,都可以指定列名称以获得元素。

在 R 中指定列名

在 R 中,可以使用$运算符前面的列名来完成此操作,如下所示:

>cal_lst$mth
[1] "jan"
> cal_lst$'mth'
[1] "jan"

在 Pandas 中指定列名

在 Pandas 中,我们以通常的方式将元素子集括在方括号中:

In [111]: cal_df['mth']
Out[111]: 'jan'

R 和 Pandas 不同的一个区域是嵌套元素的子集。 例如,要从工作日获得第 4 天,我们必须在 R 中使用[[]]运算符:

>cal_lst[[1]][[4]]
[1] 4

>cal_lst[[c(1,4)]]
[1] 4 

但是,对于 Pandas,我们可以使用双倍[]

In [132]: cal_df[1][3]
Out[132]: 4

R 的数据帧与 Pandas 的数据帧

在 R 数据帧和 Pandas 数据帧中选择数据遵循类似的脚本。 以下部分说明了我们如何从这两种方法中执行多列选择。

R 中的多列选择

在 R 中,我们通过在方括号内的向量中声明多列来指定要选择的列:

>stocks_table[c('Symbol','Price')]
Symbol  Price
1   GOOG 518.70
2   AMZN 307.82
3     FB  74.90
4   AAPL 109.70
5   TWTR  37.10
6   NFLX 334.48
7  LINKD 219.90

>stocks_table[,c('Symbol','Price')]
Symbol  Price
1   GOOG 518.70
2   AMZN 307.82
3     FB  74.90
4   AAPL 109.70
5   TWTR  37.10
6   NFLX 334.48
7  LINKD 219.90

Pandas 中的多列选择

在 Pandas 中,我们以通常的方式将元素子集括在方括号中:

In [140]: stocks_df[['Symbol','Price']]
Out[140]:Symbol Price
0        GOOG   518.70
1        AMZN   307.82
2        FB     74.90
3        AAPL   109.70
4        TWTR   37.10
5        NFLX   334.48
6        LNKD   219.90

In [145]: stocks_df.loc[:,['Symbol','Price']]
Out[145]: Symbol  Price
0         GOOG    518.70
1         AMZN    307.82
2         FB      74.90
3         AAPL    109.70
4         TWTR    37.10
5         NFLX    334.48
6         LNKD    219.90

列上的算术运算

在 R 和 pandas 中,我们可以类似的方式在数据列中应用算术运算。 因此,我们可以对两个或多个数据帧中相应位置的元素执行算术运算,例如加法或减法。

在这里,我们在 R 中构造一个带有xy列的数据帧,然后从x列中减去y列:

>norm_df<- data.frame(x=rnorm(7,0,1), y=rnorm(7,0,1))
>norm_df$x - norm_df$y
[1] -1.3870730  2.4681458 -4.6991395  0.2978311 -0.8492245  1.5851009 -1.4620324

R 中的with运算符也具有与算术运算相同的效果:

>with(norm_df,x-y)
[1] -1.3870730  2.4681458 -4.6991395  0.2978311 -0.8492245  1.5851009 -1.4620324

在 Pandas 中,可以对列执行相同的算术运算,并且等效运算符为eval

In [10]: import pandas as pd
 import numpy as np
df = pd.DataFrame({'x': np.random.normal(0,1,size=7), 'y': np.random.normal(0,1,size=7)})

In [11]: df.x-df.y
Out[11]: 0   -0.107313
 1    0.617513
 2   -1.517827
 3    0.565804
 4   -1.630534
 5    0.101900
 6    0.775186
dtype: float64

In [12]: df.eval('x-y')
Out[12]: 0   -0.107313
 1    0.617513
 2   -1.517827
 3    0.565804
 4   -1.630534
 5    0.101900
 6    0.775186
dtype: float64

聚合和分组

有时,我们可能希望将数据拆分为子集,并对每个子集应用平均值,最大值或最小值之类的函数。 在 R 中,我们可以通过aggregatetapply函数执行此操作。

在这里,我们将使用 2014 年欧洲冠军联赛半决赛进入的四家具乐部前五名前锋的统计数据集的示例。我们将使用它来说明 R 及其得分的聚合,在 Pandas 中具有等效的分组功能。

R 中的聚合

在 R 中,聚合是使用以下命令完成的:

> goal_stats=read.csv('champ_league_stats_semifinalists.csv')
>goal_stats
 Club                 Player Goals GamesPlayed
1  Atletico Madrid            Diego Costa     8           9
2  Atletico Madrid             ArdaTuran     4           9
3  Atletico Madrid            RaúlGarcía     4          12
4  Atletico Madrid           AdriánLópez     2           9
5  Atletico Madrid            Diego Godín     2          10
6      Real Madrid      Cristiano Ronaldo    17          11
7      Real Madrid            Gareth Bale     6          12
8      Real Madrid          Karim Benzema     5          11
9      Real Madrid                   Isco     3          12
10     Real Madrid         Ángel Di María     3          11
11   Bayern Munich          Thomas Müller     5          12
12   Bayern Munich           ArjenRobben     4          10
13   Bayern Munich            Mario Götze     3          11
14   Bayern Munich Bastian Schweinsteiger     3           8
15   Bayern Munich        Mario Mandžukić     3          10
16         Chelsea        Fernando Torres     4           9
17         Chelsea               Demba Ba     3           6
18         Chelsea           Samuel Eto'o     3           9
19         Chelsea            Eden Hazard     2           9
20         Chelsea                Ramires     2          10

现在,我们可以计算每个前锋的每场比赛进球数比例,以衡量他们在进球前的最后期限:

>goal_stats$GoalsPerGame<- goal_stats$Goals/goal_stats$GamesPlayed
>goal_stats
 Club   Player         Goals GamesPlayedGoalsPerGame
1  Atletico Madrid  Diego Costa     8           9    0.8888889
2  Atletico Madrid  ArdaTuran      4           9    0.4444444
3  Atletico Madrid  RaúlGarcía     4          12    0.3333333
4  Atletico Madrid  AdriánLópez    2           9    0.2222222
5  Atletico Madrid  Diego Godín     2          10    0.2000000
6  Real Madrid  Cristiano Ronaldo  17          11    1.5454545
7  Real Madrid  Gareth Bale         6          12    0.5000000
8  Real Madrid    Karim Benzema     5          11    0.4545455
9  Real Madrid       Isco           3          12    0.2500000
10 Real Madrid  Ángel Di María     3          11    0.2727273
11 Bayern Munich Thomas Müller     5          12    0.4166667
12 Bayern Munich  ArjenRobben     4          10    0.4000000
13 Bayern Munich  MarioGötze      3          11    0.2727273
14 Bayern Munich Bastian Schweinsteiger 3      8    0.3750000
15 Bayern Munich  MarioMandžukić  3          10    0.3000000
16 Chelsea       Fernando Torres   4           9    0.4444444
17 Chelsea           Demba Ba      3           6    0.5000000
18 Chelsea           Samuel Eto'o  3           9    0.3333333
19 Chelsea            Eden Hazard  2           9    0.2222222
20 Chelsea                Ramires  2          10    0.2000000

假设我们想知道每个团队的最高进球率。 我们将计算如下:

>aggregate(x=goal_stats[,c('GoalsPerGame')], by=list(goal_stats$Club),FUN=max)
 Group.1         x
1 Atletico Madrid 0.8888889
2   Bayern Munich 0.4166667
3         Chelsea 0.5000000
4     Real Madrid 1.5454545

tapply函数用于将函数应用于由一个或多个列定义的数组或向量的子集。 tapply函数还可以如下使用:

>tapply(goal_stats$GoalsPerGame,goal_stats$Club,max)
Atletico Madrid   Bayern Munich         Chelsea     Real Madrid 
 0.8888889       0.4166667       0.5000000       1.5454545

Pandas 的分组操作

在 Pandas 中,我们可以使用GroupBy函数获得相同的结果:

In [6]: import pandas as pd
importnumpy as np
In [7]: goal_stats_df=pd.read_csv('champ_league_stats_semifinalists.csv')

In [27]: goal_stats_df['GoalsPerGame']=     goal_stats_df['Goals']/goal_stats_df['GamesPlayed']

In [27]: goal_stats_df['GoalsPerGame']= goal_stats_df['Goals']/goal_stats_df['GamesPlayed']

In [28]: goal_stats_df
Out[28]: Club           Player      Goals GamesPlayedGoalsPerGame
0       Atletico Madrid Diego Costa   8       9        0.888889
1       Atletico Madrid ArdaTuran    4       9         0.444444
2       Atletico Madrid RaúlGarcía   4       12        0.333333
3       Atletico Madrid AdriánLópez  2       9         0.222222
4       Atletico Madrid Diego Godín   2       10        0.200000
5       Real Madrid  Cristiano Ronaldo 17      11        1.545455
6       Real Madrid     Gareth Bale   6       12        0.500000
7       Real Madrid     Karim Benzema 5       11        0.454545
8       Real Madrid     Isco          3       12        0.250000
9       Real Madrid     Ángel Di María 3      11        0.272727
10      Bayern Munich   Thomas Müller  5       12        0.416667
11      Bayern Munich   ArjenRobben   4       10        0.400000
12      Bayern Munich   Mario Götze    3       11        0.272727
13      Bayern Munich  BastianSchweinsteiger 3   8     0.375000
14      Bayern Munich  MarioMandžukić  3       10        0.300000
15      Chelsea        Fernando Torres  4       9         0.444444
16      Chelsea        Demba Ba         3       6         0.500000
17      Chelsea        Samuel Eto'o     3       9         0.333333
18      Chelsea        Eden Hazard      2       9         0.222222
19      Chelsea        Ramires          2       10        0.200000

In [30]: grouped = goal_stats_df.groupby('Club')

In [17]: grouped['GoalsPerGame'].aggregate(np.max)
Out[17]: Club
 Atletico Madrid    0.888889
 Bayern Munich      0.416667
 Chelsea            0.500000
 Real Madrid        1.545455
 Name: GoalsPerGame, dtype: float64

In [22]: grouped['GoalsPerGame'].apply(np.max)

Out[22]: Club
 Atletico Madrid    0.888889
 Bayern Munich      0.416667
 Chelsea            0.500000
 Real Madrid        1.545455
 Name: GoalsPerGame, dtype: float64

比较 R 和 Panda 中的匹配运算符

在这里,我们将证明 R(%in%)和 Pandas(isin())之间匹配运算符的等价性。 在这两种情况下,都会生成逻辑向量或序列(Pandas),该逻辑向量或序列指示找到匹配项的位置。

R %in%运算符

在这里,我们将演示 R 中%in%运算符的用法:

>stock_symbols=stocks_table$Symbol
>stock_symbols
[1] GOOG  AMZN  FB  AAPL  TWTR  NFLX  LINKD
Levels: AAPL AMZN FB GOOG LINKD NFLX TWTR

>stock_symbols %in% c('GOOG','NFLX')
[1]  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE

Pandas isin()函数

这是使用 Pandasisin()函数的示例:

In [11]: stock_symbols=stocks_df.Symbol
stock_symbols
Out[11]: 0    GOOG
 1    AMZN
 2      FB
 3    AAPL
 4    TWTR
 5    NFLX
 6    LNKD
 Name: Symbol, dtype: object
In [10]: stock_symbols.isin(['GOOG','NFLX'])
Out[10]: 0     True
 1    False
 2    False
 3    False
 4    False
 5     True
 6    False
 Name: Symbol, dtype: bool

逻辑子集

在 R 和 Pandas 中,执行逻辑子设置的方法不止一种。 假设我们希望显示所有平均进球数大于或等于 0.5 的球员; 也就是说,他们每两场比赛平均至少有一个进球。

R 中的逻辑子集

我们可以在 R 中执行以下操作:

  • 通过逻辑切片:

    >goal_stats[goal_stats$GoalsPerGame>=0.5,]
     Club            Player        Goals GamesPlayedGoalsPerGame
    1  Atletico Madrid Diego Costa     8           9    0.8888889
    6  Real Madrid Cristiano Ronaldo  17          11    1.5454545
    7  Real Madrid       Gareth Bale   6          12    0.5000000
    17 Chelsea          Demba Ba     3           6    0.5000000
    
    
  • 通过subset()函数:

    >subset(goal_stats,GoalsPerGame>=0.5)
     Club            Player      Goals GamesPlayedGoalsPerGame
    1  Atletico Madrid Diego Costa    8           9    0.8888889
    6  Real Madrid Cristiano Ronaldo 17          11    1.5454545
    7  Real Madrid     Gareth Bale    6          12    0.5000000
    17 Chelsea          Demba Ba     3           6    0.5000000
    
    

Pandas 中的逻辑子集

在 Pandas 中,我们也做类似的事情:

  • 逻辑切片:

    In [33]: goal_stats_df[goal_stats_df['GoalsPerGame']>=0.5]
    Out[33]:     Club        Player            Goals GamesPlayedGoalsPerGame
    0    Atletico Madrid Diego Costa     8     9          0.888889
    5    Real Madrid   Cristiano Ronaldo 17    11         1.545455
    6    Real Madrid     Gareth Bale     6     12         0.500000
    16   Chelsea         Demba Ba        3     6           0.500000
    
    
  • DataFrame.query()运算符:

    In [36]:  goal_stats_df.query('GoalsPerGame>= 0.5')
    Out[36]:
    Club              Player   Goals GamesPlayedGoalsPerGame
    0    Atletico Madrid Diego Costa   8     9            0.888889
    5    Real Madrid  Cristiano Ronaldo 17    11           1.545455
    6    Real Madrid     Gareth Bale    6     12           0.500000
    16   Chelsea         Demba Ba       3     6            0.500000
    
    

拆分合并

R 有一个名为plyr的库,用于拆分应用合并数据分析。 plyr库具有一个称为ddply的函数,该函数可用于将函数应用于数据帧的子集,然后将结果组合到另一个数据帧中。

有关ddply的更多信息,您可以参考这个内容

为说明起见,让我们考虑一个最近在 R 中创建的数据集的子集,其中包含 2013 年离开纽约市的航班数据

在 R 中执行

在这里,我们将在 R 中安装该包并实例化该库:

>install.packages('nycflights13')
...

>library('nycflights13')
>dim(flights)
[1] 336776     16

>head(flights,3)
year month day dep_timedep_delayarr_timearr_delay carrier tailnum flight
1 2013     1   1      517         2      830        11      UA  N14228   1545
2 2013     1   1      533         4      850        20      UA  N24211   1714
3 2013     1   1      542         2      923        33      AA  N619AA   1141
origindestair_time distance hour minute
1    EWR  IAH      227     1400    5     17
2    LGA  IAH      227     1416    5     33
3    JFK  MIA      160     1089    5     42

> flights.data=na.omit(flights[,c('year','month','dep_delay','arr_delay','distance')])
>flights.sample<- flights.data[sample(1:nrow(flights.data),100,replace=FALSE),]

>head(flights.sample,5)
year month dep_delayarr_delay distance
155501 2013     3         2         5      184
2410   2013     1         0         4      762
64158  2013    11        -7       -27      509
221447 2013     5        -5       -12      184
281887 2013     8        -1       -10      937

ddply函数使我们能够按年和月聚合离港延误(均值,标准差):

>ddply(flights.sample,.(year,month),summarize, mean_dep_delay=round(mean(dep_delay),2), s_dep_delay=round(sd(dep_delay),2))
year month mean_dep_delaysd_dep_delay
1  2013     1          -0.20         2.28
2  2013     2          23.85        61.63
3  2013     3          10.00        34.72
4  2013     4           0.88        12.56
5  2013     5           8.56        32.42
6  2013     6          58.14       145.78
7  2013     7          25.29        58.88
8  2013     8          25.86        59.38
9  2013     9          -0.38        10.25
10 2013    10           9.31        15.27
11 2013    11          -1.09         7.73
12 2013    12           0.00         8.58

让我们将flights.sample数据集保存到 CSV 文件中,以便我们可以使用该数据向我们展示如何在 Pandas 中执行相同的操作:

>write.csv(flights.sample,file='nycflights13_sample.csv', quote=FALSE,row.names=FALSE)

在 Pandas 中实现

为了在 Pandas 中做同样的事情,我们阅读了上一节中保存的 CSV 文件:

In [40]: flights_sample=pd.read_csv('nycflights13_sample.csv')

In [41]: flights_sample.head()
Out[41]: year   month   dep_delayarr_delay       distance
0        2013   3       2       5       184
1        2013   1       0       4       762
2        2013   11      -7      -27     509
3        2013   5       -5      -12     184
4        2013   8       -1      -10     937

通过使用GroupBy()运算符,可以达到与ddply相同的效果:

In [44]: pd.set_option('precision',3)
In [45]: grouped = flights_sample_df.groupby(['year','month'])

In [48]: grouped['dep_delay'].agg([np.mean, np.std])

Out[48]:        mean    std
year    month 
2013    1       -0.20   2.28
 2       23.85   61.63
 3       10.00   34.72
 4       0.88    12.56
 5       8.56    32.42
 6       58.14   145.78
 7       25.29   58.88
 8       25.86   59.38
 9       -0.38   10.25
 10      9.31    15.27
 11      -1.09   7.73
 12      0.00    8.58

熔化和重塑

melt函数将数据转换为宽格式,以单列组成唯一的 ID 变量组合。

R 的melt()函数

在这里,我们演示了 R 中melt()函数的用法。它生成长格式数据,其中的行是唯一的变量值组合:

>sample4=head(flights.sample,4)[c('year','month','dep_delay','arr_delay')]
> sample4
 year month dep_delay arr_delay
 155501 2013     3          2         5
 2410   2013     1          0         4
 64158  2013    11         -7       -27
 221447 2013     5         -5       -12

>melt(sample4,id=c('year','month'))
 year month  variable value
 1 2013     3 dep_delay     2
 2 2013     1 dep_delay     0
 3 2013    11 dep_delay    -7
 4 2013     5 dep_delay    -5
 5 2013     3 arr_delay     5
 6 2013     1 arr_delay     4
 7 2013    11 arr_delay   -27
 8 2013     5 arr_delay   -12
>

有关更多信息,您可以参考这个内容

Pandas 的melt()函数

在 Pandas 中,melt函数类似:

In [55]: sample_4_df=flights_sample_df[['year','month','dep_delay', \
'arr_delay']].head(4)
In [56]: sample_4_df
Out[56]:    year   month dep_delay arr_delay
 0   2013   3       2       5
 1   2013   1       0       4
 2   2013   11      -7      -27
 3   2013   5       -5      -12

In [59]: pd.melt(sample_4_df,id_vars=['year','month'])
Out[59]: year   month   variable        value
 0   2013   3       dep_delay        2
 1   2013   1       dep_delay        0
 2   2013   11      dep_delay       -7
 3   2013   5       dep_delay       -5
 4   2013   3       arr_delay        5
 5   2013   1       arr_delay        4
 6   2013   11      arr_delay       -27
 7   2013   5       arr_delay       -12

此信息的参考来自这里

因子/类别数据

R 将类别变量称为因子,并且cut()函数使我们能够将连续的数值变量划分为范围,并将范围视为因子或类别变量,或者将类别变量分类为更大的桶。

使用cut()的 R 示例

这是 R 中的示例:

clinical.trial<- data.frame(patient = 1:1000,
age = rnorm(1000, mean = 50, sd = 5),
year.enroll = sample(paste("19", 80:99, sep = ""),
 1000, replace = TRUE))

>clinical.trial<- data.frame(patient = 1:1000,
+                              age = rnorm(1000, mean = 50, sd = 5),
+                              year.enroll = sample(paste("19", 80:99, sep = ""),
+                              1000, replace = TRUE))
>summary(clinical.trial)
patient            age         year.enroll
 Min.   :   1.0   Min.   :31.14   1995   : 61 
 1st Qu.: 250.8   1st Qu.:46.77   1989   : 60 
Median : 500.5   Median :50.14   1985   : 57 
 Mean   : 500.5   Mean   :50.14   1988   : 57 
 3rd Qu.: 750.2   3rd Qu.:53.50   1990   : 56 
 Max.   :1000.0   Max.   :70.15   1991   : 55 
 (Other):654 
>ctcut<- cut(clinical.trial$age, breaks = 5)> table(ctcut)
ctcut
(31.1,38.9] (38.9,46.7] (46.7,54.6] (54.6,62.4] (62.4,70.2]
 15         232         558         186           9

可以在这个位置找到上述数据的参考。

Pandas 解决方案

这等效于先前在 Pandas 中介绍的cut()函数(仅适用于 0.15+版本):

In [79]: pd.set_option('precision',4)
clinical_trial=pd.DataFrame({'patient':range(1,1001), 
 'age' : np.random.normal(50,5,size=1000),
 'year_enroll': [str(x) for x in np.random.choice(range(1980,2000),size=1000,replace=True)]})

In [80]: clinical_trial.describe()
Out[80]:        age       patient
count   1000.000  1000.000
mean    50.089    500.500
std     4.909     288.819
min     29.944    1.000
 25%     46.572    250.750
 50%     50.314    500.500
 75%     53.320    750.250
max     63.458    1000.000

In [81]: clinical_trial.describe(include=['O'])
Out[81]:        year_enroll
count   1000
unique  20
top     1992
freq    62

In [82]: clinical_trial.year_enroll.value_counts()[:6]
Out[82]: 1992    62
 1985    61
 1986    59
 1994    59
 1983    58
 1991    58
dtype: int64
In [83]: ctcut=pd.cut(clinical_trial['age'], 5)
In [84]: ctcut.head()
Out[84]: 0    (43.349, 50.052]
 1    (50.052, 56.755]
 2    (50.052, 56.755]
 3    (43.349, 50.052]
 4    (50.052, 56.755]
 Name: age, dtype: category
 Categories (5, object): [(29.91, 36.646] < (36.646, 43.349] < (43.349, 50.052] < (50.052, 56.755] < (56.755, 63.458]]

In [85]: ctcut.value_counts().sort_index()
Out[85]: (29.91, 36.646]       3
 (36.646, 43.349]     82
 (43.349, 50.052]    396
 (50.052, 56.755]    434
 (56.755, 63.458]     85
dtype: int64

总结

在本章中,我们试图将 R 中的关键特征与其等效的 Pandas 进行比较,以实现以下目标:

  • 帮助可能希望在 Pandas 中复制相同功能的 R 用户
  • 帮助阅读了一些 R 代码,并可能希望将其重写为 Pandas 代码的任何用户

在下一章中,我们将通过对scikit-learn库进行机器学习的简要介绍来结束本书,并展示 Pandas 如何适合该框架。 可在以下位置找到本章的参考文档

十一、机器学习简介

本章将引导用户进行机器学习的旋风之旅,重点介绍如何使用 pandas 库作为可用于预处理机器学习程序使用的数据的工具。 它还向用户介绍了scikit-learn库,它是 Python 中最流行的机器学习工具包。

在本章中,我们通过将机器学习技术应用于一个众所周知的问题来进行说明,该问题涉及对上个世纪之初在泰坦尼克号灾难中幸存的乘客进行分类。 本章讨论的各个主题包括:

  • Pandas 在机器学习中的作用
  • 安装scikit-learn
  • 机器学习概念简介
  • 机器学习的应用 – Kaggle 泰坦尼克竞赛
  • Pandas 数据分析和预处理
  • 解决泰坦尼克号问题的朴素方法
  • scikit-learn ML 分类器接口
  • 监督学习算法
  • 无监督学习算法

Pandas 在机器学习中的作用

我们将考虑用于机器学习的库称为scikit-learnscikit-learn Python 库提供了广泛的机器学习算法库,可用于创建从数据输入中学习的自适应程序。

但是,在scikit-learn可以使用此数据之前,必须对其进行一些预处理。 这就是 Pandas 进来的地方。在将数据传递给scikit-learn中实现的算法之前,Pandas 可用于预处理和过滤数据。

scikit-learn 的安装

如第 2 章,“安装 Pandas 和支持软件”所述,安装 Pandas 及其随附库的最简单方法是使用第三方发行版,例如 Anaconda 来完成它。 安装scikit-learn应该没有什么不同。 我将简要介绍从 Anaconda 开始在各种平台和第三方发行版上进行安装的步骤。 scikit-learn库需要以下库:

  • Python 2.6.x 或更高版本
  • NumPy 1.6.1 或更高版本
  • SciPy 0.9 或更高

假设您已经按照第 2 章,“安装 Pandas 和支持软件”中的说明安装了 Pandas,则这些依赖项应该已经存在。

通过 Anaconda 安装

您可以通过运行 conda Python 包管理器在 Anaconda 上安装scikit-learn

conda install scikit-learn

在 Unix 上安装(Linux/MacOSX)

对于 Unix,最好从源代码安装(需要 C 编译器)。 假设已经安装了 pandas 和 NumPy,并且所需的依赖库已经到位,则可以通过运行以下命令通过 Git 安装scikit-learn

git clone https://github.com/scikit-learn/scikit-learn.git 
cd scikitlearn
python setup.py install

还可以使用PyPi中的pip将 Pandas 安装在 Unix 上:

pip install pandas

在 Windows 上安装

要在 Windows 上安装,您可以打开控制台并运行以下命令:

pip install –U scikit-learn

注意

有关安装的更多详细信息,您可以在以下位置查看官方scikit-learn文档

您还可以在这里查看scikit-learn Git 存储库的README文件。

机器学习导论

机器学习是创建从数据中学习的软件程序的艺术。 更正式地讲,它可以定义为构建使用可调参数来提高预测性能的自适应程序的实践。 它是人工智能的一个子领域。

我们可以根据他们要解决的问题的类型来分离机器学习程序。 这些问题被适当地称为学习问题。

从广义上讲,这些问题的两类分别称为有监督的学习问题和无监督的学习问题。 此外,有些混合问题的方面涉及这两个类别。

学习问题的输入由 n 行的数据集组成。 每一行代表一个样本,并且可能涉及一个或多个称为属性或特征的字段。

数据集可以规范地描述为由 n 个样本组成,每个样本由 m 个特征组成。 以下论文对机器学习进行了更详细的介绍:

关于机器学习的一些有用的知识

有监督与无监督学习

对于有监督的学习问题,学习问题的输入是由带标签数据组成的数据集。 这意味着我们具有已知其值的输出。 向学习程序提供输入样本及其对应的输出,其目的是破译它们之间的关系。 这样的输入称为标记数据。 监督学习问题包括以下内容:

  • 分类:学习的属性是类别(标称)或离散的
  • 回归:学习的属性为数字/连续

在无监督学习或数据挖掘中,向学习程序提供输入,但没有相应的输出。 该输入数据称为未标记数据。 学习程序的目标是学习或解密隐藏的标签。 这些问题包括:

  • 聚类
  • 降维

使用文档分类的概述

机器学习技术的常见用法是在文档分类领域。 机器学习的两个主要类别可以应用于此问题-有监督和无监督学习。

监督学习

输入集合中的每个文档都分配有一个类别,即标签。 学习程序/算法使用输入的文档集合来学习如何对没有标签的另一组文档进行预测。 该方法称为分类

无监督学习

输入集合中的文档未分配到类别。 因此,它们没有标签。 学习程序将其作为输入,并尝试对其聚类来发现相关或相似文档的组。 该方法称为聚类

机器学习系统如何学习

机器学习系统利用所谓的分类器从数据中学习。 分类器是采用称为特征值的矩阵并生成输出向量(也称为类)的接口。 这些特征值可以是离散的或连续的。 分类器包含三个核心组件:

  • 表示形式:这是什么类型的分类器?
  • 评估:分类器的质量如何?
  • 优化:如何在替代方案中进行搜索?

机器学习的应用 – Kaggle 泰坦尼克竞赛

为了说明我们如何在机器学习旅程的开始时使用 Pandas 来帮助我们,我们将其应用于一个经典问题,该问题位于 Kaggle 网站Kaggle 是针对机器学习问题的竞争平台。 Kaggle 背后的想法是使对使用数据解决预测分析问题感兴趣的公司将其数据发布到 Kaggle 上,并邀请数据科学家提出针对其问题的建议解决方案。 竞争可能会持续一段时间,并且竞争对手的排名会发布在排行榜上。 在比赛结束时,排名第一的选手将获得现金奖励。

为了说明如何使用scikit-learn将 Pandas 用于机器学习,我们将研究的经典问题是在 Kaggle 托管的泰坦尼克号的灾难问题,作为经典的入门机器学习问题。 问题涉及的数据集是原始数据集。 因此,在将数据提交为scikit-learn中实现的机器学习算法的输入之前,Pandas 在数据的预处理和清理中非常有用。

泰坦尼克号:灾难问题中的机器学习

泰坦尼克号的数据集包括厄运之旅的乘客清单,各种特征以及指示变量,该变量指示乘客是否幸免于沉船。 问题的本质是,在给定乘客及其相关特征的情况下,能够预测该乘客是否在泰坦尼克号沉没中幸免于难。 请删除这句话。

数据包含两个数据集:一个训练数据集和另一个测试数据集。 训练数据集包含 891 个乘客案例,测试数据集包含 491 个乘客案例。

训练数据集还包含 11 个变量,其中 10 个是特征,还有 1 个依存/指示变量Survived,它指示乘客是否幸免于难。

特征变量如下:

  • PassengerID
  • Name
  • Sex
  • Pclass(乘客舱位)
  • Carin
  • Parch(父母和子女的数量)
  • Age
  • Sibsp(兄弟姐妹数)
  • Embarked

我们可以使用 Pandas 通过以下方式帮助我们预处理数据:

  • 数据清理和某些变量的分类
  • 排除不必要的特征,这些特征显然与乘客的生存能力无关,例如,其名称
  • 处理缺失的数据

我们可以使用多种算法来解决此问题。 它们如下:

  • 决策树
  • 神经网络
  • 随机森林
  • 支持向量机

过拟合的问题

过拟合是机器学习中的一个众所周知的问题,程序会记住它作为输入输入的特定数据,从而在训练数据上获得完美的结果,而在测试数据上获得令人讨厌的结果。

为了防止过拟合,可以在训练阶段使用 10 倍交叉验证技术在数据中引入可变性。

使用 Pandas 进行数据分析和预处理

在本节中,我们将使用 Pandas 对数据进行一些分析和预处理,然后再将其作为输入提交给scikit-learn

检查数据

为了开始对数据进行预处理,让我们读取训练数据集并检查其外观。

在这里,我们将训练数据集读入 Pandas 数据帧并显示第一行:

In [2]: import pandas as pd
 import numpy as np
# For .read_csv, always use header=0 when you know row 0 is the header row
 train_df = pd.read_csv('csv/train.csv', header=0)
In [3]: train_df.head(3)

输出如下:

Examining the data

因此,我们可以看到各种特征:PassengerIdPClassNameSexAgeSibspParchTicketFareCabinEmbarked。 立即想到的一个问题是:哪些特征可能会影响乘客是否幸存?

显然,PassengerIDTicketName不应影响生存能力,因为它们是标识符变量。 我们将在分析中跳过这些内容。

处理缺失值

我们必须在数据集中进行机器学习的一个问题是如何处理训练集中的缺失值。

让我们直观地确定特征集中缺少值的位置。

为此,我们可以使用由 Tom Augspurger 编写的 R 中的missmap函数的等效项。 下图以直观的方式显示了各种特征缺少的数据量:

Handling missing values

有关更多信息和用于生成此数据的代码,请参见这个内容

我们还可以计算每个特征缺少多少数据:

In [83]: missing_perc=train_df.apply(lambda x: 100*(1-x.count().sum()/(1.0*len(x))))
In [85]: sorted_missing_perc=missing_perc.order(ascending=False)
 sorted_missing_perc
Out[85]: Cabin          77.104377
 Age            19.865320
 Embarked        0.224467
 Fare            0.000000
 Ticket          0.000000
 Parch           0.000000
 SibSp           0.000000
 Sex             0.000000
 Name            0.000000
 Pclass          0.000000
 Survived        0.000000
 PassengerId     0.000000
 dtype: float64

因此,我们可以看到大多数Cabin数据丢失了(77%),而大约 20% Age数据丢失了。 然后,我们决定从学习特征集中删除Cabin数据,因为该数据太稀疏而无用。

让我们对我们要检查的各种特征做进一步的细分。 对于分类/离散特征,我们使用条形图。 对于连续值特征,我们使用直方图:

In [137]:  import random
 bar_width=0.1
 categories_map={'Pclass':{'First':1,'Second':2, 'Third':3},
 'Sex':{'Female':'female','Male':'male'},
 'Survived':{'Perished':0,'Survived':1},
 'Embarked':{'Cherbourg':'C','Queenstown':'Q','Southampton':'S'},
 'SibSp': { str(x):x for x in [0,1,2,3,4,5,8]},
 'Parch': {str(x):x for x in range(7)}
 }
 colors=['red','green','blue','yellow','magenta','orange']
 subplots=[111,211,311,411,511,611,711,811]
 cIdx=0
 fig,ax=plt.subplots(len(subplots),figsize=(10,12))

 keyorder = ['Survived','Sex','Pclass','Embarked','SibSp','Parch']

for category_key,category_items in sorted(categories_map.iteritems(),
 key=lambda i:keyorder.index(i[0])):
 num_bars=len(category_items)
 index=np.arange(num_bars)
 idx=0
 for cat_name,cat_val in sorted(category_items.iteritems()):
 ax[cIdx].bar(idx,len(train_df[train_df[category_key]==cat_val]), label=cat_name,
 color=np.random.rand(3,1))
 idx+=1
 ax[cIdx].set_title('%s Breakdown' % category_key)
 xlabels=sorted(category_items.keys()) 
 ax[cIdx].set_xticks(index+bar_width)
 ax[cIdx].set_xticklabels(xlabels)
 ax[cIdx].set_ylabel('Count')
 cIdx +=1 
fig.subplots_adjust(hspace=0.8)
for hcat in ['Age','Fare']:
 ax[cIdx].hist(train_df[hcat].dropna(),color=np.random.rand(3,1))
 ax[cIdx].set_title('%s Breakdown' % hcat)
 #ax[cIdx].set_xlabel(hcat)
 ax[cIdx].set_ylabel('Frequency')
 cIdx +=1

fig.subplots_adjust(hspace=0.8)
plt.show()

Handling missing values

从上图中的数据和插图中,我们可以观察到以下内容:

  • 死亡人数是幸存人数的两倍(62% 对 38%)。
  • 男乘客的数量大约是女乘客的两倍(65% 对 35%)。
  • 与头等舱和二等舱相比,三等舱的乘客增加了约 20% (55% 对 45%)。
  • 大多数乘客都是单人,也就是说,船上没有孩子,父母,兄弟姐妹或配偶。

这些观察结果可能会导致我们更深入地研究并调查生存机会,性别与票价等级之间是否存在某些相关性,特别是如果我们考虑到泰坦尼克号实行的是妇女和儿童优先政策,以及泰坦尼克号所载救生艇(20 艘)比设计的(32 艘)少的事实。

有鉴于此,让我们进一步检查生存率与其中某些特征之间的关系。 我们从性别开始:

In [85]: from collections import OrderedDict
 num_passengers=len(train_df)
 num_men=len(train_df[train_df['Sex']=='male'])
 men_survived=train_df[(train_df['Survived']==1 ) & (train_df['Sex']=='male')]
 num_men_survived=len(men_survived)
 num_men_perished=num_men-num_men_survived
 num_women=num_passengers-num_men
 women_survived=train_df[(train_df['Survived']==1) & (train_df['Sex']=='female')]
 num_women_survived=len(women_survived)
 num_women_perished=num_women-num_women_survived
 gender_survival_dict=OrderedDict()
 gender_survival_dict['Survived']={'Men':num_men_survived,'Women':num_women_survived}
 gender_survival_dict['Perished']={'Men':num_men_perished,'Women':num_women_perished}
 gender_survival_dict['Survival Rate']= {'Men' : round(100.0*num_men_survived/num_men,2),'Women':round(100.0*num_women_survived/num_women,2)}
pd.DataFrame(gender_survival_dict)
Out[85]:

性别 幸存 灭亡 存活率
109 468 18.89
233 81 74.2

现在,使用以下命令以条形图说明此数据:

In [76]: #code to display survival by gender
 fig = plt.figure()
 ax = fig.add_subplot(111)
 perished_data=[num_men_perished, num_women_perished]
 survived_data=[num_men_survived, num_women_survived]
 N=2
 ind = np.arange(N)     # the x locations for the groups
 width = 0.35

 survived_rects = ax.barh(ind, survived_data, width,color='green')
 perished_rects = ax.barh(ind+width, perished_data, width,color='red')

 ax.set_xlabel('Count')
 ax.set_title('Count of Survival by Gender')
 yTickMarks = ['Men','Women']
 ax.set_yticks(ind+width)
 ytickNames = ax.set_yticklabels(yTickMarks)
 plt.setp(ytickNames, rotation=45, fontsize=10)

 ## add a legend
 ax.legend((survived_rects[0], perished_rects[0]), ('Survived', 'Perished') )
 plt.show()

上面的代码生成以下条形图:

Handling missing values

从前面的绘图中,我们可以看到,大多数女性幸存下来(74%),而大多数男性丧生(只有 19% 幸存)。

这导致我们得出以下结论:乘客的性别可能是乘客是否幸存的一个重要因素。

接下来,让我们看一下旅客舱位。 首先,我们生成三种乘客类别中每一种的幸存和死亡数据,以及生存率,并在表格中显示:

In [86]: 
from collections import OrderedDict
num_passengers=len(train_df)
num_class1=len(train_df[train_df['Pclass']==1])
class1_survived=train_df[(train_df['Survived']==1 ) & (train_df['Pclass']==1)]
num_class1_survived=len(class1_survived)
num_class1_perished=num_class1-num_class1_survived
num_class2=len(train_df[train_df['Pclass']==2])
class2_survived=train_df[(train_df['Survived']==1) & (train_df['Pclass']==2)]
num_class2_survived=len(class2_survived)
num_class2_perished=num_class2-num_class2_survived
num_class3=num_passengers-num_class1-num_class2
class3_survived=train_df[(train_df['Survived']==1 ) & (train_df['Pclass']==3)]
num_class3_survived=len(class3_survived)
num_class3_perished=num_class3-num_class3_survived
pclass_survival_dict=OrderedDict()
pclass_survival_dict['Survived']={'1st Class':num_class1_survived,
 '2nd Class':num_class2_survived,
 '3rd Class':num_class3_survived}
pclass_survival_dict['Perished']={'1st Class':num_class1_perished,
 '2nd Class':num_class2_perished,
 '3rd Class':num_class3_perished}
pclass_survival_dict['Survival Rate']= {'1st Class' : round(100.0*num_class1_survived/num_class1,2),
 '2nd Class':round(100.0*num_class2_survived/num_class2,2),
 '3rd Class':round(100.0*num_class3_survived/num_class3,2),}
pd.DataFrame(pclass_survival_dict)

Out[86]:

旅客舱位 幸存 灭亡 存活率
头等 136 80 62.96
二等 87 97 47.28
三等 119 372 24.24

然后,我们可以使用matplotlib来绘制数据,类似于前面所述的按性别划分的幸存者计数方法:

In [186]:
fig = plt.figure()
ax = fig.add_subplot(111)
perished_data=[num_class1_perished, num_class2_perished, num_class3_perished]
survived_data=[num_class1_survived, num_class2_survived, num_class3_survived]
N=3
ind = np.arange(N)                # the x locations for the groups
width = 0.35
survived_rects = ax.barh(ind, survived_data, width,color='blue')
perished_rects = ax.barh(ind+width, perished_data, width,color='red')
ax.set_xlabel('Count')
ax.set_title('Survivor Count by Passenger class')
yTickMarks = ['1st Class','2nd Class', '3rd Class']
ax.set_yticks(ind+width)
ytickNames = ax.set_yticklabels(yTickMarks)
plt.setp(ytickNames, rotation=45, fontsize=10)
## add a legend
ax.legend( (survived_rects[0], perished_rects[0]), ('Survived', 'Perished'),
 loc=10 )
plt.show()

这将产生以下条形图:

Handling missing values

从前面的数据和说明中可以清楚地看出,乘客票价等级越高,生存的机会就越大。

鉴于性别和票价舱位似乎都在影响乘客的生存机会,让我们看看将这两个特征结合并绘制两者的结合时会发生什么。 为此,我们将在 Pandas 中使用crosstab函数。

In [173]: survival_counts=pd.crosstab([train_df.Pclass,train_df.Sex],train_df.Survived.astype(bool))
 survival_counts
Out[173]:               Survived False  True
 Pclass       Sex 
 1            female    3     91
 male     77     45
 2            female    6     70
 male     91     17
 3            female   72     72
 male    300     47

现在让我们使用matplotlib显示此数据。 首先,让我们为显示目的做一些重新标记:

In [183]: survival_counts.index=survival_counts.index.set_levels([['1st', '2nd', '3rd'], ['Women', 'Men']])
In [184]: survival_counts.columns=['Perished','Survived']

现在,我们使用 Pandas 数据帧的plot函数绘制数据:

In [185]: fig = plt.figure()
 ax = fig.add_subplot(111)
 ax.set_xlabel('Count')
 ax.set_title('Survivor Count by Passenger class, Gender')
 survival_counts.plot(kind='barh',ax=ax,width=0.75,
 color=['red','black'], xlim=(0,400))
Out[185]: <matplotlib.axes._subplots.AxesSubplot at 0x7f714b187e90>

Handling missing values

泰坦尼克号问题的朴素解决方法

我们对泰坦尼克号数据进行分类的首次尝试是使用朴素但非常直观的方法。 此方法涉及以下步骤:

  1. 选择一组特征 S ,这些特征会影响一个人是否生存。
  2. 对于特征的每种可能组合,请使用训练数据指示大多数病例是否存活。 这可以在所谓的生存矩阵中进行评估。
  3. 对于我们希望预测生存的每个测试示例,请查找对应于其特征值的特征组合,并将其预测值分配给生存表中的生存值。 这种方法是朴素的 K 最近邻方法。

根据我们之前在分析中看到的内容,似乎对存活率影响最大的三个特征是:

  • Pclass
  • Sex
  • PriceBucket

我们包括与乘客等级有关的乘客票价。

生存表看起来类似于以下内容:

 NumberOfPeople  Pclass  PriceBucket     Sex  Survived
0                0       1            0  female         0
1                1       1            0    male         0
2                0       1            1  female         0
3                0       1            1    male         0
4                7       1            2  female         1
5               34       1            2    male         0
6                1       1            3  female         1
7               19       1            3    male         0
8                0       2            0  female         0
9                0       2            0    male         0
10              35       2            1  female         1
11              63       2            1    male         0
12              31       2            2  female         1
13              25       2            2    male         0
14               4       2            3  female         1
15               6       2            3    male         0
16              64       3            0  female         1
17             256       3            0    male         0
18              43       3            1  female         1
19              38       3            1    male         0
20              21       3            2  female         0
21              24       3            2    male         0
22              10       3            3  female         0
23               5       3            3    male         0

可以在随附的文件survival_data.py中找到用于生成此表的代码。 要查看我们如何使用该表,让我们看一下测试数据的摘要:

In [192]: test_df.head(3)[['PassengerId','Pclass','Sex','Fare']]
Out[192]: PassengerId   Pclass  Sex     Fare
 0        892     3       male    7.8292
 1        893     3       female  7.0000
 2        894     2       male    9.6875

对于 892 乘客,我们看到他是男性,票价为 7.8292,并且他乘坐的是三等舱。

因此,用于此乘客的生存表查找的关键字是{Sex ='male',Pclass = 3,PriceBucket = 0}(因为 7.8292 落入存储桶 0 中)。

如果我们在生存表(第 17 行)中查找与此键对应的生存值,则会看到该值是0 = Perished; 这就是我们将要预测的值。

类似地,对于乘客 893,我们有Key = {Sex = 'Female',Pclass = 3,PriceBucket = 0}

这对应于第 16 行,因此,我们将预测 1,即生存,而她的预测生存为 1,即生存。

因此,我们的结果类似于以下命令:

> head -4 csv/surv_results.csv 
PassengerId,Survived
892,0
893,1
894,0

该信息的来源位于这里

使用前面概述的生存表方法,可以在 Kaggle 上达到 0.77990 的精度。

生存表方法虽然直观,却是一种非常基本的方法,仅代表机器学习中各种可能性的冰山一角。

在以下各节中,我们将带动各种机器学习算法的旋风之旅,这些算法将帮助您(读者)对机器学习领域中可用的特征有所了解。

scikit-learn ML /分类器接口

我们将深入研究机器学习的基本原理,并通过scikit-learn基本 API 演示这些原理的用法。

scikit-learn库具有一个估计器接口。 我们通过使用线性回归模型来说明它。 例如,考虑以下内容:

In [3]: from sklearn.linear_model import LinearRegression

实例化估计器接口以创建一个模型,在这种情况下为线性回归模型:

In [4]: model = LinearRegression(normalize=True) 
In [6]: print model
 LinearRegression(copy_X=True, fit_intercept=True, normalize=True)

在这里,我们指定normalize=True,指示x值将在回归之前进行归一化。 超参数(估计器参数)在模型创建中作为参数传递。 这是创建具有可调参数的模型的示例。

当数据与估计器拟合时,从数据获得估计的参数。 让我们首先创建一些样本训练数据,这些样本训练数据通常关于y = x/2分布。 我们首先生成我们的xy值:

In [51]: sample_size=500
 x = []
 y = []

 for i in range(sample_size):
 newVal = random.normalvariate(100,10)
 x.append(newVal)
 y.append(newVal / 2.0 + random.normalvariate(50,5))

sklearnnum_samples × num_features的 2D 数组作为输入,因此我们将x数据转换为 2D 数组:

In [67]: X = np.array(x)[:,np.newaxis]
 X.shape
Out[67]: (500, 1)

在这种情况下,我们有 500 个样本和 1 个特征x。 现在,我们训练/拟合模型并显示斜率(系数)和回归线的截距,即预测:

In [71]: model.fit(X,y)
 print "coeff=%s, intercept=%s" % (model.coef_,model.intercept_)
 coeff=[ 0.47071289], intercept=52.7456611783

可以如下所示:

In [65]: plt.title("Plot of linear regression line and training data")
 plt.xlabel('x')
 plt.ylabel('y')
 plt.scatter(X,y,marker='o', color='green', label='training data');
 plt.plot(X,model.predict(X), color='red', label='regression line')
 plt.legend(loc=2)

Out[65]: [<matplotlib.lines.Line2D at 0x7f11b0752350]

The scikit-learn ML/classifier interface

要总结估计器接口的基本用法,请按照下列步骤操作:

  1. 定义模型 -- LinearRegressionSupportVectorMachineDecisionTrees等。 您可以在此步骤中指定所需的超参数。 例如,如先前指定的normalize=True
  2. 定义模型后,可以通过在上一步中定义的模型上调用fit(..)方法来对数据进行训练。
  3. 拟合模型后,可以对测试数据调用predict(..)方法以进行预测或估计。
  4. 在监督学习问题的情况下,predict(X)方法将获得未标记的观察值X,并返回预测的标记y

有关更多参考,请参见这里这里

监督学习算法

我们将简要介绍一些著名的监督学习算法,并了解如何将其应用于前面所述的泰坦尼克号生存预测问题。

使用 Patsy 为 scikit-learn 构建模型

在开始学习机器学习算法之前,我们需要了解一些Patsy库。 我们将利用Patsy设计将与scikit-learn结合使用的特征。 Patsy是用于创建称为设计矩阵的包。 这些设计矩阵是输入数据中要素的变换。 转换由称为公式的表达式指定,这些表达式对应于我们希望机器学习程序在学习中使用哪些特征的规范。

一个简单的例子如下:

假设我们希望y与其他xab变量以及ab之间的相互作用线性回归。 然后,我们可以指定模型如下:

import patsy as pts
pts.dmatrices("y ~ x + a + b + a:b", data)

在代码的前一行中,公式由以下表达式指定:y ~ x + a + b + a:b

进一步的参考,请查看这里

一般样板代码说明

在本节中,我们将介绍使用Patsyscikit-learn实现以下各种算法的样板代码。 这样做的原因是以下算法的大多数代码都是可重复的。

在以下各节中,将介绍算法的工作原理,并将提供每种算法专用的代码作为本章的附件。

  1. 首先,通过使用以下命令行来确保我们位于正确的文件夹中。 假设工作目录位于~/devel/Titanic,我们有:

    In [17]: %cd ~/devel/Titanic
     /home/youruser/devel/sandbox/Learning/Kaggle/Titanic
    
    
  2. 在这里,我们导入所需的包并阅读我们的培训和测试数据集:

    In [18]: import matplotlib.pyplot as plt
     import pandas as pd
     import numpy as np
     import patsy as pt
    In [19]: train_df = pd.read_csv('csv/train.csv', header=0)
     test_df = pd.read_csv('csv/test.csv', header=0) 
    
    
  3. 接下来,我们指定要提交给Patsy的公式:

    In [21]: formula1 = 'C(Pclass) + C(Sex) + Fare'
     formula2 = 'C(Pclass) + C(Sex)'
     formula3 = 'C(Sex)'
     formula4 = 'C(Pclass) + C(Sex) + Age + SibSp + Parch'
     formula5 = 'C(Pclass) + C(Sex) + Age + SibSp + Parch + C(Embarked)' 
     formula6 = 'C(Pclass) + C(Sex) + Age + SibSp + C(Embarked)'
     formula7 = 'C(Pclass) + C(Sex) + SibSp + Parch + C(Embarked)'
     formula8 = 'C(Pclass) + C(Sex) + SibSp + Parch + C(Embarked)'
    
    In [23]: formula_map = {'PClass_Sex_Fare' : formula1,
     'PClass_Sex' : formula2,
     'Sex' : formula3,
     'PClass_Sex_Age_Sibsp_Parch' : formula4,
     'PClass_Sex_Age_Sibsp_Parch_Embarked' : formula5,
     'PClass_Sex_Embarked' : formula6,
     'PClass_Sex_Age_Parch_Embarked' : formula7,
     'PClass_Sex_SibSp_Parch_Embarked' : formula8
     }
    
    

我们将定义一个函数来帮助我们处理缺失值。 下面的函数在数据帧中查找具有空值的单元格,获取一组相似的乘客,并将空值设置为该组相似乘客的该特征的平均值。 相似的乘客定义为与具有零特征值的乘客具有相同性别和乘客等级的乘客。

In [24]: 
def fill_null_vals(df,col_name):
 null_passengers=df[df[col_name].isnull()]
 passenger_id_list = null_passengers['PassengerId'].tolist()
 df_filled=df.copy()
 for pass_id in passenger_id_list:
 idx=df[df['PassengerId']==pass_id].index[0]
 similar_passengers = df[(df['Sex']== 
 null_passengers['Sex'][idx]) & 
 (df['Pclass']==null_passengers['Pclass'][idx])]
 mean_val = np.mean(similar_passengers[col_name].dropna())
 df_filled.loc[idx,col_name]=mean_val
 return df_filled

在这里,我们创建培训和测试数据帧的填充版本。

我们的测试数据帧是拟合的scikit-learn模型将生成的预测,以产生输出,并将其提交给 Kaggle 进行评估:

In [28]: train_df_filled=fill_null_vals(train_df,'Fare')
 train_df_filled=fill_null_vals(train_df_filled,'Age')
 assert len(train_df_filled)==len(train_df)
 test_df_filled=fill_null_vals(test_df,'Fare')
 test_df_filled=fill_null_vals(test_df_filled,'Age')
 assert len(test_df_filled)==len(test_df)

这是对scikit-learn的调用的实际实现,以通过拟合模型从训练数据中学习,然后在测试数据集上生成预测。 注意,尽管这是样板代码,但出于说明目的,实际调用了特定算法,在这种情况下为DecisionTreeClassifier

将输出数据写入具有描述性名称的文件,例如csv/dt_PClass_Sex_Age_Sibsp_Parch_1.csvcsv/dt_PClass_Sex_Fare_1.csv

In [29]: 
from sklearn import metrics,svm, tree
for formula_name, formula in formula_map.iteritems():
 print "name=%s formula=%s" % (formula_name,formula)
 y_train,X_train = pt.dmatrices('Survived ~ ' + formula, 
 train_df_filled,return_type='dataframe')
 y_train = np.ravel(y_train)
 model = tree.DecisionTreeClassifier(criterion='entropy', 
 max_depth=3,min_samples_leaf=5)
 print "About to fit..."
 dt_model = model.fit(X_train, y_train)
 print "Training score:%s" % dt_model.score(X_train,y_train)
 X_test=pt.dmatrix(formula,test_df_filled)
 predicted=dt_model.predict(X_test)
 print "predicted:%s" % predicted[:5]
 assert len(predicted)==len(test_df)
 pred_results = pd.Series(predicted,name='Survived')
 dt_results = pd.concat([test_df['PassengerId'], 
 pred_results],axis=1)
 dt_results.Survived = dt_results.Survived.astype(int)
 results_file = 'csv/dt_%s_1.csv' % (formula_name)
 print "output file: %s\n" % results_file
 dt_results.to_csv(results_file,index=False)

前面的代码遵循一个标准配方,其摘要如下:

  1. 读取训练和测试数据集
  2. 填写我们希望在两个数据集中考虑的要素的所有缺失值
  3. 为我们希望在Patsy中生成机器学习模型的各种特征组合定义公式
  4. 对于每个公式,请执行以下步骤:
    1. 调用Patsy为我们的训练特征集和训练标签集(由X_trainy_train指定)创建设计矩阵。
    2. 实例化适当的scikit-learn分类器。 在这种情况下,我们使用DecisionTreeClassifier
    3. 通过调用fit(..)方法拟合模型。
    4. 调用Patsy可以通过调用patsy.dmatrix(..)为我们的预测输出创建设计矩阵(X_test)。
    5. 预测X_test设计矩阵,并将结果保存在预测的变量中。
    6. 将我们的预测写入输出文件,该文件将提交给 Kaggle。

我们将考虑以下监督学习算法:

  • 逻辑回归
  • 支持向量机
  • 决策树
  • 随机森林

逻辑回归

在逻辑回归中,我们尝试基于一个或多个输入预测变量来预测分类变量(即离散值相关变量)的结果。

逻辑回归可以被认为等同于应用线性回归,但是适用于离散或分类变量。 但是,在二元逻辑回归(适用于泰坦尼克问题)的情况下,我们试图拟合的函数不是线性函数,因为我们仅试图预测只能采用两个值的结果- 0 和 1。使用线性函数进行回归是没有意义的,因为输出不能取 0 到 1 之间的值。理想情况下,我们需要为二进制值输出的回归建模的某种形式是值的阶跃函数 0 和 1。但是,这样的函数定义不明确且不可微,因此定义了具有更好属性的近似值:逻辑函数。 逻辑函数取 0 到 1 之间的值,但偏向 0 和 1 的极值,可以用作分类变量回归的良好近似值。

Logistic 回归函数的形式定义如下:

Logistic regression

下图很好地说明了为什么逻辑函数适合于二进制逻辑回归:

Logistic regression

我们可以看到,随着我们增加参数a的值,我们可以更接近接受 0 到 1 的值以及我们要建模的阶跃函数。 如果f(x) < 0.5,则将上述特征的一个简单应用是将输出值设置为 0,否则将其设置为 1。

用于绘制特征的代码包含在plot_logistic.py中。

可以在以下位置找到对逻辑回归的更详细的检查:http://en.wikipedia.org/wiki/Logithttp://logisticregressionanalysis.com/86-what-is-logistic-regression

在对泰坦尼克号问题进行逻辑回归时,我们希望预测一个二进制结果,即乘客是否幸存。

我们修改了样板代码,以使用scikit-learnsklearn.linear_model.LogisticRegression类。

将我们的数据提交给 Kaggle 后,获得了以下结果:

公式 Kaggle 得分
C(Pclass) + C(Sex) + Fare 0.76077
C(Pclass) + C(Sex) 0.76555
C(Sex) 0.76555
C(Pclass) + C(Sex) + Age + SibSp + Parch 0.74641
C(Pclass) + C(Sex) + Age + Sibsp + Parch + C(Embarked) 0.75598

可以在run_logistic_regression_titanic.py文件中找到实现逻辑回归的代码。

支持向量机

支持向量机SVM)是一种特征强大的监督学习算法,用于分类和回归。 它是一种判别式分类器,它在数据的聚类或分类之间绘制边界,因此可以根据新点所属的聚类对它们进行分类。

SVM 不仅可以找到边界线,还可以找到边界线。 他们还尝试确定两侧边界的边距。 SVM 算法试图找到周围具有最大可能余量的边界。

支持向量是定义边界周围最大边缘的点,请删除这些点,并可能找到较大的边缘。

因此,命名为“支持”,因为它们支持边界线周围的边距。 支持向量很重要。 如下图所示:

Support vector machine

有关此的更多信息,请参考这里

为了使用 SVM 算法进行分类,我们指定了以下三个内核之一:线性,多边形和 rbf(也称为径向基函数)。

然后,我们导入支持向量分类器SVC):

from sklearn import svm

然后,我们实例化一个 SVM 分类器,拟合模型,并预测以下内容:

model = svm.SVC(kernel=kernel)
svm_model = model.fit(X_train, y_train)
X_test = pt.dmatrix(formula, test_df_filled)
. . .

Upon submitting our data to Kaggle, the following results were obtained:

公式 核类型 Kaggle 得分
C(Pclass) + C(Sex) + Fare poly 0.71292
C(Pclass) + C(Sex) poly 0.76555
C(Sex) poly 0.76555
C(Pclass) + C(Sex) + Age + SibSp + Parch poly 0.75598
C(Pclass) + C(Sex) + Age + Parch + C(Embarked) poly 0.77512
C(Pclass) + C(Sex) + Age + Sibsp + Parch + C(Embarked) poly 0.79426
C(Pclass) + C(Sex) + Age + Sibsp + Parch + C(Embarked) rbf 0.7512

可以在以下文件中完整查看该代码:run_svm_titanic.py

在这里,我们看到内核类型为poly(多项式)且PclassSexAgeSibspParch特征组合的 SVM 在提交给 Kaggle 时产生最佳效果。 出人意料的是,似乎登机点(Embarked)以及乘客是独自旅行还是与家人一起旅行(Sibsp + Parch)确实对乘客的生存机会产生了重大影响。

后一种影响可能是由于对《铁达尼号》实行了妇女和儿童优先的政策。

决策树

决策树背后的基本思想是使用训练数据集创建决策树以进行预测。

它根据单个要素的值将训练数据集递归地分成子集。 每个拆分对应于决策树中的一个节点。 拆分过程一直进行到每个子集都是纯的为止,也就是说,所有元素都属于一个类。 这总是有效的,除非有重复的训练示例属于不同的类别。 在这种情况下,多数阶级获胜。

最终结果是用于对测试数据集进行预测的规则集。

决策树在模仿人类如何对事物进行分类的过程中对一些二进制选择进行编码,但是通过使用信息标准来决定哪个问题在每个步骤中最有用。

例如,您是否希望确定动物x是哺乳动物,鱼类还是爬行动物; 在这种情况下,我们会问以下问题:

- Does x have fur?
Yes: x is a mammal
No: Does x have feathers?
Yes: x is a bird
No: Does x have scales?
Yes: Does x have gills?
Yes: x is a fish
No: x is a reptile
No: x is an amphibian

这将生成类似于以下内容的决策树:

Decision trees

请参阅这个链接以获取更多信息:

问题在每个节点上的二进制拆分是决策树算法的本质。 决策树的主要缺点是它们可能过拟合数据。

它们是如此灵活,以至于在深度较大的情况下,它们可以记住输入,当将它们用于看不见的数据分类时,结果很差。

解决此问题的方法是使用多个决策树,这被称为使用集成估计器。 整体估计器的一个示例是随机森林算法,我们将在后面讨论。

要在scikit-learn中使用决策树,我们导入tree模块:

from sklearn import tree

We then instantiate an SVM classifier, fit the model, and predict the following:

model = tree.DecisionTreeClassifier(criterion='entropy', 
 max_depth=3,min_samples_leaf=5)
dt_model = model.fit(X_train, y_train)X_test = dt.dmatrix(formula, test_df_filled)
#. . .

将我们的数据提交给 Kaggle 后,可获得以下结果:

公式 Kaggle 得分
C(Pclass) + C(Sex) + Fare 0.77033
C(Pclass) + C(Sex) 0.76555
C(Sex) 0.76555
C(Pclass) + C(Sex) + Age + SibSp + Parch 0.76555
C(Pclass) + C(Sex) + Age + Parch + C(Embarked) 0.78947
C(Pclass) + C(Sex) + Age + Sibsp + Parch + C(Embarked) 0.79426

随机森林

随机森林和决策树一样,都是非参数模型的一个例子。 随机森林基于决策树。 决策边界是从数据本身获悉的。 它不必是直线,多项式或径向基函数。 随机森林模型建立在决策树概念的基础上,产生大量决策树或森林。 它从数据中随机抽取一个样本,并识别出一组特征以增长每个决策树。 在决策树集之间比较模型的错误率,以找到产生最强分类模型的特征集。

要在scikit-learn中使用随机森林,我们导入RandomForestClassifier模块:

from sklearn import RandomForestClassifier

然后,我们实例化random forest分类器,拟合模型,并预测以下内容:

model = RandomForestClassifier(n_estimators=num_estimators, 
 random_state=0)
rf_model = model.fit(X_train, y_train)
X_test = dt.dmatrix(formula, test_df_filled)
. . .

将我们的数据提交给 Kaggle(公式:C(Pclass) + C(Sex) + Age + Sibsp + Parch + C(Embarked)),可获得以下结果:

公式 Kaggle 得分
10 0.74163
100 0.76077
1000 0.76077
10000 0.77990
100000 0.77990

无监督学习算法

在无监督学习中,我们最关心两个任务:降维和聚类。

降维

降维用于帮助系统地可视化高维数据。 这很有用,因为我们的人脑只能看到三个空间维度(可能是一个时间维度),但是大多数数据集涉及的维度要高得多。

用于降维的典型技术是主成分分析PCA)。 PCA 涉及使用线性代数技术将高维数据投影到低维空间上。 这不可避免地涉及信息丢失,但是通常通过按正确的维数和维数进行投影,可以使信息丢失最小化。 一种常见的降维技术是在我们的数据中找到解释最大差异(信息的代理)的变量组合,并沿这些维度进行投影。

在无监督学习问题的情况下,我们没有标签集(Y),因此,我们仅对输入数据X本身调用fit(),对于 PCA,我们将调用transform()而不是transform(),因为我们正在尝试将数据转换为新的表示形式。

我们将用来演示 USL 的数据集之一是鸢尾花数据集,它可能是所有机器学习中最著名的数据集。

scikit-learn库提供了一组预打包的数据集,可通过sklearn.datasets模块使用。 鸢尾花数据集就是其中之一。

鸢尾花数据集由来自三种不同种类的鸢尾花(杂色,山和弗吉尼亚)的 150 个数据样本组成,每种类型有 50 个样本。 数据集包含四个要素/维度:

  • 花瓣长度
  • 花瓣宽度
  • 萼片长度
  • 萼片宽度

长度和宽度值以厘米为单位。 可以按以下方式加载:

from sklearn.datasets import load_iris 
iris = load_iris()

在我们的无监督学习检查中,我们将专注于如何可视化和聚类数据。

在讨论无监督学习之前,让我们先检查一下鸢尾花数据。 load_iris()命令返回所谓的束对象,该对象本质上是一个字典,除了包含数据的键之外,还包含键。 因此,我们有以下内容:

In [2]: iris_data.keys()
Out[2]: ['target_names', 'data', 'target', 'DESCR', 'feature_names']

此外,数据本身看起来类似于以下内容:

In [3]: iris_data.data.shape
Out[3]: (150, 4)

这对应于四个特征的 150 个样本。 这四个特征如下所示:

In [4]: print iris_data.feature_names
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

我们还可以看一下实际数据:

In [9]: print iris_data.data[:2]
[[ 5.1  3.5  1.4  0.2]
 [ 4.9  3\.   1.4  0.2]]

我们的目标名称(我们要尝试预测的名称)看起来类似于以下内容:

In [10]: print iris_data.target_names
 ['setosa' 'versicolor' 'virginica']

如前所述,鸢尾花特征集对应于五维数据,因此我们无法在色图上对其进行可视化。 我们可以做的一件事是选择两个特征并将它们相互绘制,同时使用颜色区分物种特征。 接下来,我们对所有可能的特征组合进行此操作,一次为一组六个不同的可能性选择两个。 这些组合如下:

  • 萼片宽度与花瓣长度
  • 萼片宽度与花瓣宽度
  • 萼片宽度与花瓣长度
  • 萼片长度与花瓣宽度
  • 萼片长度与花瓣长度
  • 花瓣宽度与花瓣长度

Dimensionality reduction

可以在以下文件中找到用于此目的的代码:display_iris_dimensions.py。 从前面的图中可以看出,山鸢尾点倾向于自身聚类,而弗吉尼亚点和杂色点之间有一些重叠。 这可能使我们得出结论,后两个物种彼此之间的联系比与山鸢尾物种的联系更紧密。

但是,这些是二维数据切片。 如果我们想更全面地查看数据,并用一些表示的所有萼片和花瓣尺寸的视图,该怎么办?

如果在我们的二维图未显示的尺寸之间存在迄今尚未发现的联系怎么办? 有一种可视化的方法吗? 输入降维。 我们将使用降维来提取萼片和花瓣尺寸的两个组合,以帮助可视化。

我们可以应用降维来做到这一点,如下所示:

In [118]: X, y = iris_data.data, iris_data.target
 from sklearn.decomposition import PCA
 pca = PCA(n_components=2)
 pca.fit(X)
 X_red=pca.transform(X)
 print "Shape of reduced dataset:%s" % str(X_red.shape)

 Shape of reduced dataset:(150, 2)

因此,我们看到缩小的数据集现在是二维的。 让我们以二维方式直观地显示数据,如下所示:

In [136]: figsize(8,6)
 fig=plt.figure()
 fig.suptitle("Dimensionality reduction on iris data")
 ax=fig.add_subplot(1,1,1)
 colors=['red','yellow','magenta']
 cols=[colors[i] for i in iris_data.target]
 ax.scatter(X_red[:,0],X[:,1],c=cols)
Out[136]:
<matplotlib.collections.PathCollection at 0x7fde7fae07d0>

Dimensionality reduction

我们可以检查 PCA 缩减的两个维度的构成,如下所示:

In [57]:
print "Dimension Composition:"
idx=1
for comp in pca.components_:
 print "Dim %s" % idx
 print " + ".join("%.2f x %s" % (value, name)
 for value, name in zip(comp, iris_data.feature_names))
 idx += 1

Dimension Composition:
Dim 1
0.36 x sepal length (cm) + -0.08 x sepal width (cm) + 0.86 x petal length (cm) + 0.36 x petal width (cm)
Dim 2
-0.66 x sepal length (cm) + -0.73 x sepal width (cm) + 0.18 x petal length (cm) + 0.07 x petal width (cm)

因此,我们可以看到两个减小的尺寸是所有四个萼片和花瓣尺寸的线性组合。

该信息的来源位于这里

K 均值聚类

聚类背后的想法是根据给定的标准将数据集中的相似点分组在一起,从而在数据中找到聚类。

K 均值算法旨在将一组数据点划分为 K 个聚类,以使每个数据点都属于具有最近均值或质心的聚类。

为了说明 K 均值聚类,我们可以将其应用于通过 PCA 获得的减少鸢尾花数据集,但是在这种情况下,我们不像在监督学习中那样将实际标签传递给fit(..)方法:

In [142]: from sklearn.cluster import KMeans
 k_means = KMeans(n_clusters=3, random_state=0)
 k_means.fit(X_red)
 y_pred = k_means.predict(X_red)

现在,我们显示聚类数据,如下所示:

In [145]: figsize(8,6)
 fig=plt.figure()
 fig.suptitle("K-Means clustering on PCA-reduced iris data, K=3")
 ax=fig.add_subplot(1,1,1)
 ax.scatter(X_red[:, 0], X_red[:, 1], c=y_pred);

K-means clustering

请注意,我们的 K 均值算法集群并不完全对应于通过 PCA 获得的维度。 可从这里获得源代码。

有关scikit-learn中 K 均值聚类的更多信息,通常可以在以下位置找到:http://scikit-learn.org/stable/auto_examples/cluster/plot_cluster_iris.htmlhttp://en.wikipedia.org/wiki/K-means_clustering

总结

在本章中,我们展开了机器学习的旋风之旅,研究了 Pandas 在特征提取,选择和工程中的作用,以及学习了机器学习中的关键概念,例如监督学习与无监督学习。 我们还简要介绍了两种机器学习方法中的一些关键算法,并使用scikit-learn包利用这些算法来学习数据并做出预测。 本章并不是要对机器学习进行全面的介绍,而是要说明如何使用 Pandas 来协助机器学习领域的用户。

二、Pandas 安装和支持软件

在我们开始对 Pandas 进行数据分析之前,我们需要确保已安装该软件并且环境处于正确的工作状态。 本节介绍了 Python(如有必要),pandas 库以及 Windows,MacOSX 和 Linux 平台的所有必需依赖项的安装。 我们讨论的主题包括:

  • 选择 Python 版本
  • 安装 Python
  • 安装 Pandas(0.16.0)
  • 安装 IPython 和 Virtualenv

以下部分概述的步骤在大多数情况下应该有效,但是您的里程可能会因设置而异。 在不同的操作系统版本上,脚本可能无法始终完美运行,并且系统中已经存在的第三方包有时可能与提供的说明冲突。

选择要使用的 Python 版本

在继续安装和下载 Python 和 Pandas 之前,我们需要考虑将要使用的 Python 版本。 当前,当前使用的 Python 有两种版本,分别是 Python 2.7.x 和 Python3。如果读者是 Python 和 Pandas 的新手,那么问题就变成了他/她应该采用哪种语言。

从表面上看,Python 3.x 似乎是更好的选择,因为 Python 2.7.x 被认为是传统,而 Python 3.x 被认为是该语言的未来。

注意

作为参考,您可以浏览标题为“Python2 或 Python3”的文档

Python 2.x 和 3 之间的主要区别包括 Python3 中更好的 Unicode 支持,将printexec更改为函数以及整数除法。 有关更多详细信息,请参见 Python 3.0 的新增功能

但是,出于科学,数值或数据分析的目的,建议使用 Python 2.7 而不是 Python3,原因如下:Python 2.7 是大多数当前发行版的首选版本,并且对某些库的 Python 3.x 支持不那么强, 尽管这已不再是一个问题。

作为参考,请查看标题为科学家们将迁移到 Python3 吗?的文档。 因此,本书将使用 Python 2.7。 它并不排除使用 Python3,使用 Python3 的开发人员可以通过参考以下文档轻松地对示例进行必要的代码更改:将 Python2 代码移植到 Python3

Python 安装

在这里,我们详细介绍了在多个平台上安装 Python 的情况 -- Linux,Windows 和 MacOSX。

Linux

如果您使用的是 Linux,则很可能预装了 Python。 如果不确定,请在命令提示符下键入以下内容:

 which python

根据您的发行版和特定的安装情况,很可能在 Linux 的以下文件夹之一中找到 Python:

  • /usr/bin/python
  • /bin/python
  • /usr/local/bin/python
  • /opt/local/bin/python

您可以通过在命令提示符下键入以下命令来确定安装了哪个特定版本的 Python:

python --version

如果尚未安装 Python,这种情况极少发生,您需要确定所使用的 Linux 版本,然后下载并安装它。 这是安装命令以及各种 Linux Python 发行版的链接:

  1. Debian/Ubuntu (14.04)

     sudo apt-get install Python 2.7
     sudo apt-get install Python 2.7-devel
    
    

    Debian Python 页面位于这里

  2. Redhat Fedora/Centos/RHEL

     sudo yum install python
     sudo yum install python-devel
    
    

    Fedora 软件安装在这个页面上。

  3. OpenSuse

     sudo zypper install python
     sudo zypper install python-devel
    
    

    有关安装软件的更多信息,请参见这里

  4. Slackware:对于此 Linux 发行版,最好下载压缩的 tarball 并从源代码中安装它,如以下部分所述。

从压缩 tarball 安装 Python

如果上述方法都不适合您,您还可以下载压缩的 tarball(XZ 或 Gzip)并安装。 以下是有关这些步骤的简要概述:

#Install dependencies
sudo apt-get install build-essential
sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
#Download the tarball
mkdir /tmp/downloads
cd /tmp/downloads
wget http://python.org/ftp/python/2.7.5/Python-2.7.5.tgz
tar xvfz Python-2.7.5.tgz
cd Python-2.7.5
# Configure, build and install
./configure --prefix=/opt/Python 2.7 --enable-shared
make
make test
sudo make install
echo "/opt/Python 2.7/lib" >> /etc/ld.so.conf.d/opt-Python 2.7.conf
ldconfig
cd ..
rm -rf /tmp/downloads

有关此信息,请参见 Python 下载页面

Windows

与 Linux 和 Mac 发行版不同,Python 未预先安装在 Windows 上。

核心 Python 安装

标准方法是使用来自 CPython 团队的 Windows 安装程序,它们是 MSI 包。 可从此处下载 MSI 包。

根据您的 Windows 版本是 32 位还是 64 位,选择适当的 Windows 包。 默认情况下,Python 被安装到包含版本号的文件夹中,因此在这种情况下,它将被安装到以下位置:C:\Python27

这使您可以运行多个版本的 Python 而不会出现问题。 安装后,应将以下文件夹添加到PATH环境变量:C:\Python27\C:\Python27\Tools\Scripts

第三方 Python 软件安装

为了使其他包(例如 pandas)的安装更加容易,需要安装一些 Python 工具。 安装 SetuptoolsPIP。 Setuptools 对于安装其他 Python 包(例如 pandas)非常有用。 它增加了标准 Python 发行版中distutils工具提供的打包和安装功能。

要安装 Setuptools,请从这个链接下载ez_setup.py脚本。

然后,将其保存到C:\Python27\Tools\Scripts

然后,运行ez_setup.pyC:\Python27\Tools\Scripts\ez_setup.py

关联的命令pip为开发人员提供了易于使用的命令,该命令可以快速轻松地安装 Python 模块。 从这个链接下载get-pip脚本。

然后,从以下位置运行它:C:\Python27\Tools\Scripts\get-pip.py

作为参考,您还可以浏览标题为在 Windows 上安装 Python 的文档。

Windows 上还有第三方 Python 提供商,这些任务使安装任务变得更加容易。 它们列出如下:

MacOSX

MacOSX 的当前和最新发行版(过去 5 年)中已预安装了 Python 2.7。可以在 Mac 上的以下文件夹中找到由 Apple 提供的预安装版本:

  • /System/Library/Frameworks/Python.framework
  • /usr/bin/python

但是,您可以从这个链接安装自己的版本。 一个需要注意的是,您现在将拥有两个 Python 安装,并且必须小心确保路径和环境完全分开。

使用包管理器进行安装

也可以使用 Mac 上的包管理器(例如 Macports 或 Homebrew)安装 Python。 我将在这里讨论使用 Homebrew 进行的安装,因为这似乎是最方便用户的操作。 作为参考,您可以浏览标题为在 MacOSX 上安装 Python的文档。 步骤如下:

  1. 安装 Homebrew 并运行:

    ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
    
    

    然后,您需要在PATH环境变量的顶部添加 Homebrew 文件夹。

  2. 在 Unix 提示符下安装 Python 2.7:

    brew install python
    
    
  3. 安装第三方软件:分发并点子。 安装 Homebrew 会自动安装这些包。 分发和 PIP 使一个人可以轻松下载和安装/卸载 Python 包。

从第三方供应商安装 Python 和 Pandas

安装 Python,Pandas 及其相关依赖项的最直接方法是使用第三方供应商(如 Enthought 或 Continuum Analytics)安装打包的发行版。

我以前更喜欢 Continuum Analytics Anaconda 而不是 Enthought,因为 Anaconda 是免费赠送的,而 Enthought 过去是为完全访问其所有数字模块收取订阅费用。 但是,在最新版的 Enthought Canopy 中,几乎没有办法将这两个发行版分开。 不过,我个人比较喜欢 Anaconda,因此将介绍其安装版本。

作为参考,请参见 Anaconda Python 发行版。 现在,我将简要介绍 Anaconda 包及其安装方法。

Continuum Analytics Anaconda

Anaconda 是免费的 Python 发行版,专注于大规模数据处理,分析和数值计算。 以下是 Anaconda 的主要功能:

  • 它包括最受欢迎的 Python 包,用于科学,工程,数值和数据分析。
  • 它是完全免费的,并且可在 Linux,Windows 和 MacOSX 平台上使用。
  • 安装不需要 root 或本地 admin 特权,并且整个包都安装在一个文件夹中。
  • 多个安装可以共存,并且该安装不会影响系统上预先存在的 Python 安装。
  • 它包括诸如 Cython,NumPy,SciPy,pandas,IPython,matplotlib 之类的模块,以及自产的 Continuum 包,如 Numba,Blaze 和 Bokeh。

有关此的更多信息,请参考这个链接

安装 Anaconda

以下说明详细说明了如何在所有三个平台上安装 Anaconda。 下载位置是这里。 Python 的版本默认为 Anaconda 中的 Python 2.7。

Linux

执行以下步骤进行安装:

  1. 从下载位置下载 Linux 安装程序(32/64 位)。

  2. 在终端中,运行以下命令:

    bash <Linux installer file>
    
    

    例如,bash Anaconda-1.8.0-Linux-x86_64.sh

  3. 接受许可条款。

  4. 指定安装位置。 我倾向于在本地第三方软件安装中使用$HOME/local

MacOSX

执行以下步骤进行安装:

  1. 从下载位置下载 Mac 安装程序(.pkg file - 64-bit)。
  2. 双击.pkg文件进行安装,然后按照弹出窗口中的说明进行操作。 例如,包文件名:Anaconda-1.8.0-MacOSX-x86_64.pkg

Windows

在 Windows 环境中执行以下步骤:

  1. 从下载位置下载 Windows 安装程序(.exe file - 32/64-bit)。
  2. 双击.pkg文件进行安装,然后按照弹出窗口中的说明进行操作。 例如,包文件名:Anaconda-1.8.0-MacOSX-x86_64.pkg

所有平台的最后一步

作为快捷方式,您可以将ANACONDA_HOME定义为安装 Anaconda 的文件夹。 例如,在我的 Linux 和 MacOSX 安装中,我具有以下环境变量设置:

ANACONDA_HOME=$HOME/local/anaconda

在 Windows 上,如下所示:

set ANACONDA_HOME=C:\Anaconda

将 Anaconda bin文件夹添加到PATH环境变量。 如果您希望默认使用 Python Anaconda,可以通过确保$ANACONDA_HOME/bin在包含 System Python 的文件夹之前的PATH变量的开头来实现。 如果您不想默认使用 Anaconda Python,则有以下两种选择:

  1. 每次根据需要激活 Anaconda 环境。 可以执行以下操作:

    source $HOME/local/anaconda/bin/activate $ANACONDA_HOME
    
    
  2. 为 Anaconda 创建一个单独的环境。 这可以通过使用内置的conda命令来完成

有关更多信息,请阅读 Conda 文档。 可以从 Anaconda 安装页面获得更详细的安装 Anaconda 的说明。

下载并安装 Pandas

pandas 库是 Python 语言的一部分,因此我们现在可以继续安装 pandas。 在撰写本书时,可用的 Pandas 的最新稳定版本是 0.12 版。 各种依赖项以及相关的下载位置如下:

是否必需 描述 下载位置
NumPy : 1.6.1 or higher 必需 用于数值运算的 NumPy 库 http://www.numpy.org/
python-dateutil 1.5 必需 日期操作和工具库 http://labix.org/
Pytz 必需 时区支持 http://sourceforge.net/
numexpr 可选,推荐 加快数值运算 https://code.google.com/
bottleneck 可选,推荐 性能相关 http://berkeleyanalytics.com/
Cython 可选,推荐 用于优化的 Python C 扩展 http://cython.org/
SciPy 可选,推荐 适用于 Python 的科学工具集 http://scipy.org/
PyTables 可选 基于 HDF5 的存储库 http://pytables.github.io/
matplotlib 可选,推荐 类似于 Matlab 的 Python 绘图库 http://sourceforge.net/
statsmodels 可选 Python 的统计模块 http://sourceforge.net/
openpyxl 可选 读取/写入 Excel 文件的库 https://www.python.org/
xlrd/xlwt 可选 读取/写入 Excel 文件的库 http://python-excel.org/
boto 可选 用于访问 Amazon S3 的库 https://www.python.org/
BeautifulSouphtml5liblxml中的一个 可选 read_html()函数运行所需的库 http://www.crummy.com
html5lib 可选 用于解析 HTML 的库 https://pypi.python.org/pypi/html5lib
lmxl 可选 用于处理 XML 和 HTML 的 Python 库 http://lxml.de/

Linux

对于流行的 Linux 版本,安装 pandas 非常简单。 首先,请确保已安装 Python .dev文件。 如果不是,则按照下一节中的说明安装它们。

Ubuntu/Debian

对于 Ubantu/Debian 环境,运行以下命令:

sudo apt-get install python-dev

RedHat

对于 RedHat 环境,运行以下命令:

yum install python-dev

现在,我将向您展示如何安装 Pandas。

Ubuntu/Debian

要在 Ubuntu/Debian 环境中安装 Pandas,请运行以下命令:

sudo apt-get install python-pandas

Fedora

对于 Fedora,运行以下命令:

sudo yum install python-pandas

OpenSuse

通过 YaST 软件管理安装python-pandas或使用以下命令:

sudo zypper install python-pandas

有时,先前的安装可能需要附加的依赖关系,尤其是在 Fedora 的情况下。 在这种情况下,您可以尝试安装其他依赖项:

sudo yum install gcc-gfortran gcc44-gfortran libgfortran lapack blas python-devel
sudo python-pip install numpy

MacOSX

在 MacOSX 上有多种安装 Pandas 的方法。以下各节中将对它们进行说明。

源码安装

Pandas 有一些依赖项使其正常工作,一些是必需的,而另一些则是可选的,尽管某些理想的功能需要正常工作。 这将安装所有必需的依赖项:

  1. 安装easy_install程序:

    wget http://python-distribute.org/distribute_setup.pysudo python distribute_setup.py
    
    
  2. 安装 Cython

    sudo easy_install -U Cython
    
    
  3. 然后,您可以从源代码进行安装,如下所示:

          git clone git://github.com/pydata/pandas.git
          cd pandas
          sudo python setup.py install
    

二进制安装

如果已按照 Python 安装部分中的说明安装了 PIP,则安装 pandas 的过程如下所示:

pip install pandas

Windows

以下方法描述了 Windows 环境中的安装。

二进制安装

确保首先安装numpypython-dateutilpytz。 每个模块都需要运行以下命令:

  • 对于python-dateutil

    C:\Python27\Scripts\pip install python-dateutil
    
    
  • 对于pytz

    C:\Python27\Scripts\pip install pytz 
    
    

从二进制文件下载进行安装,然后从这里运行适用于 Windows 版本的二进制文件。 例如,如果您的处理器是 AMD64,则可以使用以下命令下载并安装 Pandas:

  1. 下载以下文件:(适用于 Pandas 0.16)

    pandas-0.16.1-cp26-none-win_amd64.whl (md5)
    
  2. 通过pip安装下载的文件:

    pip install  
    pandas-0.16.1-cp26-none-win_amd64.whl
    

要测试安装,请运行 Python 并在命令提示符下键入以下内容:

import pandas

如果返回没有错误,则说明安装成功。

源码安装

此处的步骤完全解释了安装:

  1. 按照标题为附录:在 Windows 上安装MinGW编译器的文档中的说明。

  2. 确保将MingW二进制位置添加到附加了C:\MingW\binPATH变量中。

  3. 安装CythonNumpy

    可以从这里下载并安装Numpy

    可以从这里下载和安装Cython

安装Cython的步骤如下:

  • 通过 PIP 安装:

    C:\Python27\Scripts\pip install Cython
    
  • 直接下载:

    1. 从 GitHub 下载并安装 pandas 源代码

    2. 您只需下载 zip 文件并将其解压缩到合适的文件夹中即可。

    3. 转到包含 Pandas 下载to C:\python27\python的文件夹,然后运行setup.py install

    4. 有时,在运行setup.py时可能会出现以下错误:

      distutils.errors.DistutilsError: Setup script exited with error:
      Unable to find vcvarsall.bat
      
      

这可能与未正确指定mingw作为编译器有关。 检查您是否再次按照所有步骤进行操作。

从源头在 Windows 上安装 Pandas 容易出现许多错误和错误,因此不建议这样做。

IPython

交互式 PythonIPython)是一个非常有用的工具,可用于使用 Python 进行数据分析,并在此处提供安装步骤的简要说明。 IPython 提供了一个比标准 Python 提示有用得多的交互式环境。 其功能包括:

  • 制表符补全可帮助用户进行数据浏览。
  • 全面的帮助功能,使用object_name?打印有关对象的详细信息。
  • 魔术函数使用户能够使用%run魔术命令在 IPython 中运行操作系统命令,并运行 Python 脚本并将其数据加载到 IPython 环境中。
  • 通过_____变量,%history和其他魔术功能以及上下箭头键的历史功能。

有关更多信息,请参见文档

IPython 笔记本

IPython Notebook 是启用 Web 的 IPython 版本。 它使用户可以将代码,数值计算以及显示图形和富媒体组合在一个文档中,即笔记本。 笔记本可以与同事共享,并转换为 HTML/PDF 格式。 有关更多信息,请参考标题为 IPython 笔记本的文档。 这是一个例子:

IPython Notebook

PYMC Pandas 示例的先前图片来自这里

IPython 安装

推荐的安装 IPython 的方法是使用第三方包,例如 Continuum 的 Anaconda 或 Enthought Canopy。

Linux

假设已按照说明安装了 Pandas 和其他用于科学计算的工具,则以下单行命令就足够了:

对于 Ubuntu/Debian,请使用

sudo apt-get install ipython-notebook

对于 Fedora,请使用

sudo yum install python-ipython-notebook

如果已安装pipsetuptools,也可以通过以下命令将其安装在 Linux/Mac 平台上:

sudo pip install ipython

Windows

IPython 在 Windows 上需要setuptoolsPyReadline库。 PyReadline是 GNU readline库的 Python 实现。 要在 Windows 上安装 IPython,请执行以下步骤:

  1. 如上一节中所述安装setuptools
  2. 通过从 PyPI Readline 包页面下载 MS Windows 安装程序来安装pyreadline
  3. GitHub IPython 下载位置下载并运行 IPython 安装程序。

有关更多信息,请参见 IPython 安装页面

MacOSX

可以使用pipsetuptools将 IPython 安装在 MacOSX 上。 它还需要readlinezeromq库,最好使用 Homebrew 进行安装。 步骤如下:

brew install readline
brew install zeromq
pip install ipython pyzmq tornado pygments

pyzmqtornadopygments模块是获得 IPython 笔记本的完整图形功能所必需的。 有关更多信息,请参见标题为为 OSX 设置 IPython 笔记本和 Pandas 的文档。

通过 Anaconda 安装(对于 Linux/MacOSX)

假设已经安装了 Anaconda,只需运行以下命令即可将 IPython 更新到最新版本:

conda update conda
conda update ipython

Continuum Analytics 的 Wakari

如果用户还没有准备好安装 IPython,则可以选择在云中使用 IPython。 输入 Wakari,这是一个基于云的分析解决方案,为 Continuum 服务器上托管的 IPython 笔记本电脑提供全面支持。 它允许用户在云上的浏览器中全部创建,编辑,保存和共享 IPython 笔记本。 可以在这个链接中找到更多详细信息。

Virtualenv

Virtualenv 是用于创建隔离的 Python 环境的工具。 如果您希望在不影响标准 Python 构建的环境中测试最新版本的 Pandas,这将很有用。

Virtualenv 的安装和使用

我只建议您在决定不安装和使用Anaconda包的情况下安装 Virtualenv,因为它已经提供了 Virtualenv 功能。 简要步骤如下:

  1. 通过pip安装:

    pip install virtualenv
    
    
  2. 使用 Virtualenv

    • 使用以下命令创建虚拟环境:

       virtualenv newEnv
      
      
    • 使用以下命令激活虚拟环境:

       source newEnv/bin/activate
      
      
    • 使用以下命令停用虚拟环境并返回到标准 Python 环境:

       deactivate
      
      

有关此的更多信息,可以浏览标题为虚拟环境的文档。

提示

下载示例代码

您可以从 GitHub 存储库下载代码。

其他以数字或分析为重点的 Python 发行版

以下是各种与第三方数据分析相关的 Python 发行版的摘要。 以下所有发行版均包含 Pandas:

  • Continuum Analytics Anaconda:免费的企业级 Python 发行版,专注于大规模数据处理,分析和数值计算。 有关详细信息,请参阅这里
  • PythonXY:免费的面向科学和工程的 Python 发行版,用于数值计算,数据分析和可视化。 它基于 Qt GUI 包和 Spyder 交互式科学开发环境。 有关更多信息,请参阅这里
  • WinPython:针对 Windows 平台的免费开源 Python 发行版,专注于科学计算。 有关更多信息,请参考这里

有关 Python 发行版的更多信息,请访问这里

总结

有两个主要的 Python 版本:Python 2.7.x 和 Python 3.x。 目前,Python 2.7.x 更成熟,因此更适合进行数据分析和数值计算。 为了正确设置,pandas 库需要一些依赖项– NumPy,SciPy 和 matplotlib 仅举几例。 有很多安装 Pandas 的方法–建议的方法是安装包括 Pandas 在内的第三方发行版之一。 发行版包括 Continuum 发行的 Anaconda,Enthough Canopy,WinPython 和 PythonXY。 强烈建议安装 IPython 包,因为它为数据分析提供了一个丰富,高度交互的环境。

因此,设置我们的学习 Pandas 的环境包括安装合适版本的 Python,安装 Pandas 及其相关模块,以及设置一些有用的工具,例如 IPython。 再强调一遍,我强烈建议读者通过安装 Anaconda 或 Enthought 等第三方发行版来帮自己一个忙,并使他们的工作更轻松,从而使他们的环境在尽可能短的时间内运行并无故障运行。 。 在下一章中,我们将直接研究 Pandas 的主要特征。

三、Pandas 数据结构

本章是本书中最重要的部分。 现在,我们将开始研究 Pandas 的肉和骨头。 我们首先浏览 NumPy ndarrays,这是一种不在 Pandas 中而是 NumPy 的数据结构。 NumPy ndarrays的知识很有用,因为它构成了 Pandas 数据结构的基础。 NumPy 数组的另一个主要优点是它们执行称为向量化的操作,这些操作需要在 Python 数组上遍历/循环的操作要快得多。

我们将在本章中介绍的主题包括:

  • 浏览numpy.ndarray数据结构。
  • pandas.Series一维1D)Pandas 数据结构
  • pandas.DatcaFrame二维2D)Pandas 表格数据结构
  • pandas.Panel3 维3D)Pandas 数据结构

在本章中,我将通过使用 IPython(一个基于浏览器的界面,使用户可以交互地向 Python 解释器键入命令)的众多示例来介绍这些资料。 上一章提供了安装 IPython 的说明。

NumPy ndarray

NumPy 库是一个非常重要的包,用于使用 Python 进行数值计算。 其主要功能包括:

  • numpy.ndarray类型,同构多维数组
  • 访问大量数学函数 – 线性代数,统计信息等
  • 能够集成 C,C++ 和 Fortran 代码

有关 NumPy 的更多信息,请参见这里

NumPy 中的主要数据结构是数组类ndarray。 它是元素的齐次多维(n 维)表,它们像常规数组一样由整数索引。 但是,numpy.ndarray(也称为numpy.array)与标准 Python array.array类不同,后者提供的功能要少得多。 这里提供了有关各种操作的更多信息。

NumPy 数组创建

可以通过调用各种 NumPy 方法以多种方式创建 NumPy 数组。

numpy.array和 NumPy 数组

NumPy 数组可以直接通过numpy.array构造器创建:

In [1]: import numpy as np
In [2]: ar1=np.array([0,1,2,3])# 1 dimensional array
In [3]: ar2=np.array ([[0,3,5],[2,8,7]]) # 2D array
In [4]: ar1
Out[4]: array([0, 1, 2, 3])
In [5]: ar2
Out[5]: array([[0, 3, 5],
 [2, 8, 7]])

数组的形状通过ndarray.shape给出:

In [5]: ar2.shape
Out[5]: (2, 3)

尺寸数量使用ndarray.ndim获得:

In [7]: ar2.ndim
Out[7]: 2

numpy.arange和 NumPy 数组

ndarray.arange是 Python 的range函数的 NumPy 版本:In [10]:产生从 0 到 11 的整数,不包括 12。

In [10]: ar3=np.arange(12); ar3
Out[10]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
In [11]: # start, end (exclusive), step size
 ar4=np.arange(3,10,3); ar4
Out[11]: array([3, 6, 9])

numpy.linspace和 NumPy 数组

ndarray.linspace在起点和终点之间生成线性均匀间隔的元素:

In [13]:# args - start element,end element, number of elements
 ar5=np.linspace(0,2.0/3,4); ar5
Out[13]:array([ 0.,  0.22222222,  0.44444444,  0.66666667])

各种其他函数和 NumPy 数组

这些函数包括numpy.zerosnumpy.onesnumpy.eyenrandom.randnumpy.random.randnnumpy.empty

在每种情况下,该参数都必须是一个元组。 对于一维数组,您只需指定元素数,而无需元组。

numpy.ones

以下命令行说明了该函数:

In [14]:# Produces 2x3x2 array of 1's.
 ar7=np.ones((2,3,2)); ar7
Out[14]: array([[[ 1.,  1.],
 [ 1.,  1.],
 [ 1.,  1.]],
 [[ 1.,  1.],
 [ 1.,  1.],
 [ 1.,  1.]]])

numpy.zeros

以下命令行说明了该函数:

In [15]:# Produce 4x2 array of zeros.
 ar8=np.zeros((4,2));ar8
Out[15]: array([[ 0.,  0.],
 [ 0.,  0.], 
 [ 0.,  0.],
 [ 0.,  0.]])

numpy.eye

以下命令行说明了该函数:

In [17]:# Produces identity matrix
 ar9 = np.eye(3);ar9
Out[17]: array([[ 1.,  0.,  0.],
 [ 0.,  1.,  0.],
 [ 0.,  0.,  1.]])

numpy.diag

以下命令行说明了该函数:

In [18]: # Create diagonal array
 ar10=np.diag((2,1,4,6));ar10
Out[18]: array([[2, 0, 0, 0],
 [0, 1, 0, 0],
 [0, 0, 4, 0],
 [0, 0, 0, 6]])

numpy.random.rand

以下命令行说明了该函数:

In [19]: # Using the rand, randn functions
 # rand(m) produces uniformly distributed random numbers with range 0 to m
 np.random.seed(100)   # Set seed
 ar11=np.random.rand(3); ar11
Out[19]: array([ 0.54340494,  0.27836939,  0.42451759])
In [20]: # randn(m) produces m normally distributed (Gaussian) random numbers
 ar12=np.random.rand(5); ar12
Out[20]: array([ 0.35467445, -0.78606433, -0.2318722 ,    0.20797568,  0.93580797])

numpy.empty

使用np.empty创建未初始化的数组比分配np.onesnp.zerosmalloccmalloc)是一种更便宜,更快捷的分配数组的方法。 但是,只有在确定所有元素稍后都会初始化时,才应使用它:

In [21]: ar13=np.empty((3,2)); ar13
Out[21]: array([[ -2.68156159e+154,   1.28822983e-231],
 [  4.22764845e-307,   2.78310358e-309],
 [  2.68156175e+154,   4.17201483e-309]])

np.tile

np.tile函数允许通过根据参数重复几次来从较小的数组构造一个数组:

In [334]: np.array([[1,2],[6,7]])
Out[334]: array([[1, 2],
 [6, 7]])
In [335]: np.tile(np.array([[1,2],[6,7]]),3)
Out[335]: array([[1, 2, 1, 2, 1, 2],
 [6, 7, 6, 7, 6, 7]])
In [336]: np.tile(np.array([[1,2],[6,7]]),(2,2))
Out[336]: array([[1, 2, 1, 2],
 [6, 7, 6, 7],
 [1, 2, 1, 2],
 [6, 7, 6, 7]])

NumPy 数据类型

我们可以使用dtype参数指定数字数组的内容类型:

In [50]: ar=np.array([2,-1,6,3],dtype='float'); ar
Out[50]: array([ 2., -1.,  6.,  3.])
In [51]: ar.dtype
Out[51]: dtype('float64')
In [52]: ar=np.array([2,4,6,8]); ar.dtype
Out[52]: dtype('int64')
In [53]: ar=np.array([2.,4,6,8]); ar.dtype
Out[53]: dtype('float64')

NumPy 中的默认dtypefloat。 对于字符串,dtype是数组中最长字符串的长度:

In [56]: sar=np.array(['Goodbye','Welcome','Tata','Goodnight']); sar.dtype
Out[56]: dtype('S9')

您不能在 NumPy 中创建长度可变的字符串,因为 NumPy 需要知道为该字符串分配多少空间。 dtypes也可以是布尔值,复数等等:

In [57]: bar=np.array([True, False, True]); bar.dtype
Out[57]: dtype('bool')

ndarray的数据类型可以用与其他语言(例如 Java 或 C/C++)相同的方式进行更改。 例如,floatint等。 执行此操作的机制是使用numpy.ndarray.astype()函数。 这是一个例子:

In [3]: f_ar = np.array([3,-2,8.18])
 f_ar
Out[3]: array([ 3\.  , -2\.  ,  8.18])
In [4]: f_ar.astype(int)
Out[4]: array([ 3, -2,  8])

有关转换的更多信息,请参见官方文档

NumPy 索引和切片

NumPy 中的数组索引以0开头,例如 Python,Java 和 C++ 之类的语言,而 Fortran,Matlab 和 Octave 的数组索引以1开头。 数组可以以标准方式建立索引,就像我们将索引到任何其他 Python 序列中一样:

# print entire array, element 0, element 1, last element.
In [36]: ar = np.arange(5); print ar; ar[0], ar[1], ar[-1]
[0 1 2 3 4]
Out[36]: (0, 1, 4)
# 2nd, last and 1st elements
In [65]: ar=np.arange(5); ar[1], ar[-1], ar[0]
Out[65]: (1, 4, 0)

可以使用::-1惯用法反转数组,如下所示:

In [24]: ar=np.arange(5); ar[::-1]
Out[24]: array([4, 3, 2, 1, 0])

多维数组使用整数元组建立索引:

In [71]: ar = np.array([[2,3,4],[9,8,7],[11,12,13]]); ar
Out[71]: array([[ 2,  3,  4],
 [ 9,  8,  7],
 [11, 12, 13]])
In [72]: ar[1,1]
Out[72]: 8

在这里,我们将row1column1的条目设置为5

In [75]: ar[1,1]=5; ar
Out[75]: array([[ 2,  3,  4],
 [ 9,  5,  7],
 [11, 12, 13]])

检索第 2 行:

In [76]:  ar[2]
Out[76]: array([11, 12, 13])
In [77]: ar[2,:]
Out[77]: array([11, 12, 13])

检索列 1:

In [78]: ar[:,1]
Out[78]: array([ 3,  5, 12])

如果指定的索引超出数组范围,则将引发IndexError

In [6]: ar = np.array([0,1,2])
In [7]: ar[5]
 ---------------------------------------------------------------------------
 IndexError                  Traceback (most recent call last)
 <ipython-input-7-8ef7e0800b7a> in <module>()
 ----> 1 ar[5]
 IndexError: index 5 is out of bounds for axis 0 with size 3

因此,对于 2D 数组,第一维表示行,第二维表示列。 冒号(:)表示对维度所有元素的选择。

数组切片

可以使用以下语法对数组进行切片:ar[startIndex: endIndex: stepValue]

In [82]: ar=2*np.arange(6); ar
Out[82]: array([ 0,  2,  4,  6,  8, 10])
In [85]: ar[1:5:2]
Out[85]: array([2, 6])

请注意,如果我们希望包含endIndex值,则需要超过它,如下所示:

In [86]: ar[1:6:2]
Out[86]: array([ 2,  6, 10])

使用ar[:n]获得前 n 个元素:

In [91]: ar[:4]
Out[91]: array([0, 2, 4, 6])

这里的隐含假设是startIndex=0, step=1

从元素 4 开始直到结束:

In [92]: ar[4:]
Out[92]: array([ 8, 10])

stepValue=3的切片数组:

In [94]: ar[::3]
Out[94]: array([0, 6])

为了说明 NumPy 中索引的范围,让我们参考此图,该图取自 SciPy 2013 上的 NumPy 演讲,可在这个链接中找到:

Array slicing

现在让我们检查上图中的表达式的含义:

  • 表达式a[0,3:5]表示从第 0 行和第 3-5 列开始,其中不包括第 5 列。
  • 在表达式a[4:,4:]中,前 4 个表示第 4 行的起点,并将给出所有列,即数组[[40, 41, 42, 43, 44, 45], [50, 51, 52, 53, 54, 55]]。 第二个 4 显示了在第 4 列开始处的截止,以产生数组[[44, 45], [54, 55]]
  • 表达式a[:,2]给出了列 2 中的所有行。
  • 现在,在最后一个表达式a[2::2,::2]中,2::2指示起点在第 2 行,此处的步长值也为 2。这将为我们提供数组[[20, 21, 22, 23, 24, 25], [40, 41, 42, 43, 44, 45]。 此外,::2指定我们以 2 的步骤检索列,从而产生最终结果数组([[20, 22, 24], [40, 42, 44]])。

可以将分配和切片结合在一起,如以下代码片段所示:

In [96]: ar
Out[96]: array([ 0,  2,  4,  6,  8, 10])
In [100]: ar[:3]=1; ar
Out[100]: array([ 1,  1,  1,  6,  8, 10])
In [110]: ar[2:]=np.ones(4);ar
Out[110]: array([1, 1, 1, 1, 1, 1])

数组遮罩

在这里,NumPy 数组可用作遮罩,以选择或滤除原始数组的元素。 例如,请参见以下代码段:

In [146]: np.random.seed(10)
 ar=np.random.random_integers(0,25,10); ar
Out[146]: array([ 9,  4, 15,  0, 17, 25, 16, 17,  8,  9])
In [147]: evenMask=(ar % 2==0); evenMask
Out[147]: array([False,  True, False,  True, False, False,  True, False,  True, False], dtype=bool)
In [148]: evenNums=ar[evenMask]; evenNums
Out[148]: array([ 4,  0, 16,  8])

在下面的示例中,我们随机生成一个 0 到 25 之间的 10 个整数的数组。然后,我们创建一个布尔掩码数组,该数组用于仅滤除偶数。 例如,如果我们希望通过将默认值替换为缺失值来消除缺失值,则此掩码功能可能非常有用。 在这里,缺失值''被替换为'USA'作为默认国家/地区。 请注意,''也是一个空字符串:

In [149]: ar=np.array(['Hungary','Nigeria', 
 'Guatemala','','Poland',
 '','Japan']); ar
Out[149]: array(['Hungary', 'Nigeria', 'Guatemala', 
 '', 'Poland', '', 'Japan'], 
 dtype='|S9')
In [150]: ar[ar=='']='USA'; ar
Out[150]: array(['Hungary', 'Nigeria', 'Guatemala', 
 'USA', 'Poland', 'USA', 'Japan'], dtype='|S9')

整数数组也可以用于索引一个数组以生成另一个数组。 请注意,这会产生多个值。 因此,输出必须是ndarray类型的数组。 以下代码段对此进行了说明:

In [173]: ar=11*np.arange(0,10); ar
Out[173]: array([ 0, 11, 22, 33, 44, 55, 66, 77, 88, 99])
In [174]: ar[[1,3,4,2,7]]
Out[174]: array([11, 33, 44, 22, 77])

在前面的代码中,选择对象是一个列表,并且选择了索引 1、3、4、2 和 7 的元素。 现在,假设我们将其更改为以下内容:

In [175]: ar[1,3,4,2,7]

由于数组是一维的,因此我们收到IndexError错误,并且指定的索引太多,无法访问它。

IndexError          Traceback (most recent call last)
<ipython-input-175-adbcbe3b3cdc> in <module>()
----> 1 ar[1,3,4,2,7]

IndexError: too many indices

数组索引也可以进行此分配,如下所示:

In [176]: ar[[1,3]]=50; ar
Out[176]: array([ 0, 50, 22, 50, 44, 55, 66, 77, 88, 99])

通过使用数组索引列表从另一个数组创建新数组时,新数组具有相同的形状。

复杂索引

在这里,我们说明了如何使用复杂的索引将值从较小的数组分配到较大的数组:

In [188]: ar=np.arange(15); ar
Out[188]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])

In [193]: ar2=np.arange(0,-10,-1)[::-1]; ar2
Out[193]: array([-9, -8, -7, -6, -5, -4, -3, -2, -1,  0])

切出ar的前 10 个元素,并用ar2中的元素替换它们,如下所示:

In [194]: ar[:10]=ar2; ar
Out[194]: array([-9, -8, -7, -6, -5, -4, -3, -2, -1,  0, 10, 11, 12, 13, 14])

副本和视图

NumPy 数组上的视图只是描绘其包含的数据的一种特殊方式。 创建视图不会导致数组的新副本,而是可以按特定顺序排列其中包含的数据,或者仅显示某些数据行。 因此,如果将数据替换为基础数组的数据,则无论何时通过索引访问数据,这都会反映在视图中。

切片时不会将初始数组复制到内存中,因此效率更高。 np.may_share_memory方法可用于查看两个数组是否共享同一存储块。 但是,应谨慎使用,因为它可能会产生误报。 修改视图会修改原始数组:

In [118]:ar1=np.arange(12); ar1
Out[118]:array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

In [119]:ar2=ar1[::2]; ar2
Out[119]: array([ 0,  2,  4,  6,  8, 10])

In [120]: ar2[1]=-1; ar1
Out[120]: array([ 0,  1, -1,  3,  4,  5,  6,  7,  8,  9, 10, 11])

为了强制 NumPy 复制数组,我们使用np.copy函数。 正如我们在以下数组中看到的,修改复制的数组时,原始数组不受影响:

In [124]: ar=np.arange(8);ar
Out[124]: array([0, 1, 2, 3, 4, 5, 6, 7])

In [126]: arc=ar[:3].copy(); arc
Out[126]: array([0, 1, 2])

In [127]: arc[0]=-1; arc
Out[127]: array([-1,  1,  2])

In [128]: ar
Out[128]: array([0, 1, 2, 3, 4, 5, 6, 7])

操作

在这里,我们介绍 NumPy 中的各种操作。

基本操作

基本算术运算使用标量操作数逐个元素地工作。 它们是- +-*/**

In [196]: ar=np.arange(0,7)*5; ar
Out[196]: array([ 0,  5, 10, 15, 20, 25, 30])

In [198]: ar=np.arange(5) ** 4 ; ar
Out[198]: array([  0,   1,  16,  81, 256])

In [199]: ar ** 0.5
Out[199]: array([  0.,   1.,   4.,   9.,  16.])

当另一个数组是第二个操作数时,操作也按元素方式工作,如下所示:

In [209]: ar=3+np.arange(0, 30,3); ar
Out[209]: array([ 3,  6,  9, 12, 15, 18, 21, 24, 27, 30])

In [210]: ar2=np.arange(1,11); ar2
Out[210]: array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])

在下面的代码段中,我们看到了逐元素的减法,除法和乘法:

In [211]: ar-ar2
Out[211]: array([ 2,  4,  6,  8, 10, 12, 14, 16, 18, 20])

In [212]: ar/ar2
Out[212]: array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3])

In [213]: ar*ar2
Out[213]: array([  3,  12,  27,  48,  75, 108, 147, 192, 243, 300])

使用 NumPy 进行此操作比使用纯 Python 更快。 IPython 中的%timeit函数被称为魔术函数,它使用 Python timeit模块来定时执行 Python 语句或表达式,其解释如下:

In [214]: ar=np.arange(1000)
 %timeit ar**3
 100000 loops, best of 3: 5.4 µs per loop

In [215]:ar=range(1000)
 %timeit [ar[i]**3 for i in ar]
 1000 loops, best of 3: 199 µs per loop

数组乘法与矩阵乘法不同; 它是元素方式的,意味着相应的元素被相乘在一起。 对于矩阵乘法,请使用点运算符。 有关更多信息,请参考这里

In [228]: ar=np.array([[1,1],[1,1]]); ar
Out[228]: array([[1, 1],
 [1, 1]])

In [230]: ar2=np.array([[2,2],[2,2]]); ar2
Out[230]: array([[2, 2],
 [2, 2]])

In [232]: ar.dot(ar2)
Out[232]: array([[4, 4],

 [4, 4]])

比较和逻辑运算也是基于元素的:

In [235]: ar=np.arange(1,5); ar
Out[235]: array([1, 2, 3, 4])

In [238]: ar2=np.arange(5,1,-1);ar2
Out[238]: array([5, 4, 3, 2])

In [241]: ar < ar2
Out[241]: array([ True,  True, False, False], dtype=bool)

In [242]: l1 = np.array([True,False,True,False])
 l2 = np.array([False,False,True, False])
 np.logical_and(l1,l2)
Out[242]: array([False, False,  True, False], dtype=bool)

其他 NumPy 运算(例如logsincosexp)也是按元素排列的:

In [244]: ar=np.array([np.pi, np.pi/2]); np.sin(ar)
Out[244]: array([  1.22464680e-16,   1.00000000e+00])

请注意,对于在两个 NumPy 数组上的按元素进行操作,两个数组必须为具有相同的形状,否则将导致错误,因为该操作的参数必须是两个数组中的对应元素:

In [245]: ar=np.arange(0,6); ar
Out[245]: array([0, 1, 2, 3, 4, 5])

In [246]: ar2=np.arange(0,8); ar2
Out[246]: array([0, 1, 2, 3, 4, 5, 6, 7])

In [247]: ar*ar2
 ---------------------------------------------------------------------------
 ValueError                                Traceback (most recent call last)
 <ipython-input-247-2c3240f67b63> in <module>()
 ----> 1 ar*ar2
 ValueError: operands could not be broadcast together with shapes (6) (8)

此外,NumPy 数组可以如下进行转置:

In [249]: ar=np.array([[1,2,3],[4,5,6]]); ar
Out[249]: array([[1, 2, 3],
 [4, 5, 6]])

In [250]:ar.T
Out[250]:array([[1, 4],
 [2, 5],
 [3, 6]])

In [251]: np.transpose(ar)
Out[251]: array([[1, 4],
 [2, 5],
 [3, 6]])

假设我们希望不按元素比较而是按数组比较数组。 我们可以通过使用np.array_equal运算符实现以下目标:

In [254]: ar=np.arange(0,6)
 ar2=np.array([0,1,2,3,4,5])
 np.array_equal(ar, ar2)
Out[254]: True

在这里,我们看到返回的是布尔值而不是布尔数组。 仅当两个数组中的全部对应元素匹配时,该值才为True。 前面的表达式等效于以下内容:

In [24]: np.all(ar==ar2)
Out[24]: True

归约操作

诸如np.sumnp.prod之类的运算符对数组执行归约运算。 也就是说,它们将多个元素组合为一个值:

In [257]: ar=np.arange(1,5)
 ar.prod()
Out[257]: 24

在多维数组的情况下,我们可以使用axis参数指定是要按行还是按列应用约简运算符:

In [259]: ar=np.array([np.arange(1,6),np.arange(1,6)]);ar
Out[259]: array([[1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5]])
# Columns
In [261]: np.prod(ar,axis=0)
Out[261]: array([ 1,  4,  9, 16, 25])
# Rows
In [262]: np.prod(ar,axis=1)
Out[262]: array([120, 120])

对于多维数组,不指定轴会导致将操作应用于数组的所有元素,如以下示例中所述:

In [268]: ar=np.array([[2,3,4],[5,6,7],[8,9,10]]); ar.sum()
Out[268]: 54

In [269]: ar.mean()
Out[269]: 6.0
In [271]: np.median(ar)
Out[271]: 6.0

统计运算符

这些运算符用于将标准统计运算应用于 NumPy 数组。 名称是不言自明的:np.std()np.mean()np.median()np.cumsum()

In [309]: np.random.seed(10)
 ar=np.random.randint(0,10, size=(4,5));ar
Out[309]: array([[9, 4, 0, 1, 9],
 [0, 1, 8, 9, 0],
 [8, 6, 4, 3, 0],
 [4, 6, 8, 1, 8]])
In [310]: ar.mean()
Out[310]: 4.4500000000000002

In [311]: ar.std()
Out[311]: 3.4274626183227732

In [312]: ar.var(axis=0)  # across rows
Out[312]: array([ 12.6875,   4.1875,  11\.    ,  10.75  ,  18.1875])

In [313]: ar.cumsum()
Out[313]: array([ 9, 13, 13, 14, 23, 23, 24, 32, 41, 41, 49, 55, 
 59, 62, 62, 66, 72, 80, 81, 89])

逻辑运算符

逻辑运算符可用于数组比较/检查。 它们如下:

  • np.all():用于计算所有元素的逐元素 AND
  • np.any():用于计算所有元素的逐元素 OR

生成ints4×4随机数组,并检查是否有任何元素可以被 7 整除,并且所有元素都小于 11:

In [320]: np.random.seed(100)
 ar=np.random.randint(1,10, size=(4,4));ar
Out[320]: array([[9, 9, 4, 8],
 [8, 1, 5, 3],
 [6, 3, 3, 3],
 [2, 1, 9, 5]])

In [318]: np.any((ar%7)==0)
Out[318]: False

In [319]: np.all(ar<11)
Out[319]: True

广播

在广播中,我们利用 NumPy 组合形状不完全相同的数组的功能。 这是一个例子:

In [357]: ar=np.ones([3,2]); ar
Out[357]: array([[ 1.,  1.],
 [ 1.,  1.],
 [ 1.,  1.]])

In [358]: ar2=np.array([2,3]); ar2
Out[358]: array([2, 3])

In [359]: ar+ar2
Out[359]: array([[ 3.,  4.],
 [ 3.,  4.],
 [ 3.,  4.]])

因此,我们可以看到,通过将ar2添加到ar的每一行中,从而产生广播。 这是另一个示例,显示广播在各个维度上均有效:

In [369]: ar=np.array([[23,24,25]]); ar
Out[369]: array([[23, 24, 25]])
In [368]: ar.T
Out[368]: array([[23],
 [24],
 [25]])
In [370]: ar.T+ar
Out[370]: array([[46, 47, 48],
 [47, 48, 49],
 [48, 49, 50]])

在这里,广播了行和列数组,最后得到了3×3数组。

数组形状处理

数组的形状处理有许多步骤。

展平多维数组

np.ravel()函数允许您按以下方式展平多维数组:

In [385]: ar=np.array([np.arange(1,6), np.arange(10,15)]); ar
Out[385]: array([[ 1,  2,  3,  4,  5],
 [10, 11, 12, 13, 14]])

In [386]: ar.ravel()
Out[386]: array([ 1,  2,  3,  4,  5, 10, 11, 12, 13, 14])

In [387]: ar.T.ravel()
Out[387]: array([ 1, 10,  2, 11,  3, 12,  4, 13,  5, 14])

您还可以使用np.flatten进行相同的操作,除了它在np.ravel返回视图的同时返回副本。

重塑

整形函数可用于更改数组的形状或使其不展平:

In [389]: ar=np.arange(1,16);ar
Out[389]: array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15])
In [390]: ar.reshape(3,5)
Out[390]: array([[ 1,  2,  3,  4,  5],
 [ 6,  7,  8,  9, 10],
 [11, 12, 13, 14, 15]])

np.reshape函数返回数据视图,表示基础数组保持不变。 但是,在特殊情况下,如果不复制数据,则无法更改形状。 有关此的更多详细信息,请参见文档

调整大小

有两个大小调整操作符,numpy.ndarray.resize是用于调整大小的ndarray操作符,numpy.resize是用于返回具有指定形状的新数组的numpy.resize。 在这里,我们说明numpy.ndarray.resize函数:

In [408]: ar=np.arange(5); ar.resize((8,));ar
Out[408]: array([0, 1, 2, 3, 4, 0, 0, 0])

请注意,只有在没有其他引用此数组的情况下,此函数才起作用。 否则,ValueError结果:

In [34]: ar=np.arange(5); 
 ar
Out[34]: array([0, 1, 2, 3, 4])
In [35]: ar2=ar
In [36]: ar.resize((8,));
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-36-394f7795e2d1> in <module>()
----> 1 ar.resize((8,));

ValueError: cannot resize an array that references or is referenced by another array in this way.  Use the resize function 

解决此问题的方法是改用numpy.resize函数:

In [38]: np.resize(ar,(8,))
Out[38]: array([0, 1, 2, 3, 4, 0, 1, 2])

添加大小

np.newaxis函数为数组添加了额外的维度:

In [377]: ar=np.array([14,15,16]); ar.shape
Out[377]: (3,)
In [378]: ar
Out[378]: array([14, 15, 16])
In [379]: ar=ar[:, np.newaxis]; ar.shape
Out[379]: (3, 1)
In [380]: ar
Out[380]: array([[14],
 [15],
 [16]])

数组排序

数组可以以多种方式排序。

  1. 沿轴对数组排序; 首先,让我们沿 y 轴进行讨论:

    In [43]: ar=np.array([[3,2],[10,-1]])
     ar
    Out[43]: array([[ 3,  2],
     [10, -1]])
    In [44]: ar.sort(axis=1)
     ar
    Out[44]: array([[ 2,  3],
     [-1, 10]])
    
    
  2. 在这里,我们将解释沿 x 轴的排序:

    In [45]: ar=np.array([[3,2],[10,-1]])
     ar
    Out[45]: array([[ 3,  2],
     [10, -1]])
    In [46]: ar.sort(axis=0)
     ar
    Out[46]: array([[ 3, -1],
     [10,  2]])
    
    
  3. 按就地(np.array.sort)和就地(np.sort)函数排序。

  4. 可用于数组排序的其他操作包括:

    • np.min():返回数组中的最小元素
    • np.max():返回数组中的最大元素
    • np.std():返回数组中元素的标准差
    • np.var():它返回数组中元素的方差
    • np.argmin():最小索引
    • np.argmax():最大索引
    • np.all():返回所有元素的按元素 AND
    • np.any():返回所有元素的按元素 OR

Pandas 中的数据结构

Pandas 由 Wed McKinney 于 2008 年创建,原因是他在 R 中处理时间序列数据时遇到挫折。它是在 NumPy 之上构建的,并提供了其中不可用的功能。 它提供了快速,易于理解的数据结构,并有助于填补 Python 与 R 之类的语言之间的空白。

我在此处演示的各种操作的关键参考是官方的 Pandas 数据结构文档

Pandas 有三种主要的数据结构:

  • 序列
  • 数据帧
  • 面板

序列

序列实际上是引擎盖下的一维 NumPy 数组。 它由一个 NumPy 数组和一个标签数组组成。

序列创建

创建序列数据结构的一般构造如下:

import pandas as pd 
ser=pd.Series(data, index=idx)

数据可以是以下之一:

  • ndarray
  • Python 字典
  • 标量值

使用numpy.ndarray

在这种情况下,索引必须与数据长度相同。 如果未指定索引,则将创建以下默认索引[0,... n-1],其中n是数据的长度。 下面的示例创建一个由 0 至 1 之间的七个随机数组成的序列结构; 未指定索引:

In [466]: import numpy as np
 np.random.seed(100)
 ser=pd.Series(np.random.rand(7)); ser
Out[466]:0    0.543405
 1    0.278369
 2    0.424518
 3    0.844776
 4    0.004719
 5    0.121569
 6    0.670749
 dtype: float64

以下示例使用指定的月份名称索引创建一年中前 5 个月的序列结构:

In [481]: import calendar as cal
 monthNames=[cal.month_name[i] for i in np.arange(1,6)]
 months=pd.Series(np.arrange(1,6),index=monthNames);months

Out[481]: January     1
 February    2
 March       3
 April       4
 May         5
 dtype: int64

In [482]: months.index
Out[482]: Index([u'January', u'February', u'March', u'April', u'May'], dtype=object)

使用 Python 字典

如果数据是字典并提供了索引,则将从中构造标签; 否则,字典的键将用作标签。 字典的值用于填充序列结构。

In [486]: currDict={'US' : 'dollar', 'UK' : 'pound', 
 'Germany': 'euro', 'Mexico':'peso',
 'Nigeria':'naira',
 'China':'yuan', 'Japan':'yen'}
 currSeries=pd.Series(currDict); currSeries
Out[486]: China        yuan
 Germany      euro
 Japan         yen
 Mexico       peso
 Nigeria     naira
 UK          pound
 US         dollar
 dtype: object

Pandas 序列结构的索引类型为pandas.core.index.Index,可以将其视为有序多集。

在以下情况下,我们指定一个索引,但是该索引包含一个条目,该条目不是相应的dict中的键。 结果是将将的值分配为NaN,表明它丢失了。 我们将在后面的部分中处理缺失值。

In [488]: stockPrices = {'GOOG':1180.97,'FB':62.57, 
 'TWTR': 64.50, 'AMZN':358.69,
 'AAPL':500.6}
 stockPriceSeries=pd.Series(stockPrices,
 index=['GOOG','FB','YHOO', 
 'TWTR','AMZN','AAPL'],
 name='stockPrices')
 stockPriceSeries
Out[488]: GOOG    1180.97
 FB        62.57
 YHOO        NaN
 TWTR      64.50
 AMZN     358.69
 AAPL     500.60
 Name: stockPrices, dtype: float64

请注意,序列还具有可以如前面的片段中所示设置的name属性。 name属性在将序列对象组合到数据帧结构等任务中很有用。

使用标量值

对于标量数据,必须提供索引。 将为尽可能多的索引值重复该值。 此方法的一种可能用途是提供一种快速而肮脏的初始化方法,并在以后填充序列结构。 让我们看看如何使用标量值创建序列:

In [491]: dogSeries=pd.Series('chihuahua', 
                   index=['breed','countryOfOrigin',
 'name', 'gender'])
 dogSeries
Out[491]: breed              chihuahua
 countryOfOrigin    chihuahua
 name               chihuahua
 gender             chihuahua
 dtype: object

无法提供索引只会导致返回标量值,如下所示:

In [494]: dogSeries=pd.Series('pekingese'); dogSeries
Out[494]: 'pekingese'

In [495]: type(dogSeries)
Out[495]: str

序列操作

序列的行为与上一节中讨论的numpy数组的行为非常相似,其中一个警告是切片等操作也会对索引进行切片。

赋值

可以使用类似于字典的方式使用索引标签设置和访问值:

In [503]: currDict['China']
Out[503]: 'yuan'

In [505]: stockPriceSeries['GOOG']=1200.0
 stockPriceSeries
Out[505]: GOOG    1200.00
 FB        62.57
 YHOO        NaN
 TWTR      64.50
 AMZN     358.69
 AAPL     500.60
 dtype: float64

dict一样,如果尝试检索丢失的标签,则会引发KeyError

In [506]: stockPriceSeries['MSFT']
KeyError: 'MSFT'

通过显式使用get可以避免此错误,如下所示:

In [507]: stockPriceSeries.get('MSFT',np.NaN)
Out[507]: nan

在这种情况下,将默认值np.NaN指定为序列结构中不存在该键时要返回的值。

切片

切片操作的行为与 NumPy 数组相同:

In [498]: stockPriceSeries[:4]
Out[498]: GOOG    1180.97
 FB        62.57
 YHOO        NaN
 TWTR      64.50
 dtype: float64

逻辑切片也如下工作:

In [500]: stockPriceSeries[stockPriceSeries > 100]
Out[500]: GOOG    1180.97
 AMZN     358.69
 AAPL     500.60
 dtype: float64

其他操作

可以应用算术和统计运算,就像使用 NumPy 数组一样:

In [501]: np.mean(stockPriceSeries)
Out[501]: 433.46600000000001
In [502]: np.std(stockPriceSeries)
Out[502]: 410.50223047384287

按元素操作也可以按顺序执行:

In [506]: ser
Out[506]: 0    0.543405
 1    0.278369
 2    0.424518
 3    0.844776
 4    0.004719
 5    0.121569
 6    0.670749
 dtype: float64
In [508]: ser*ser
Out[508]: 0    0.295289
 1    0.077490
 2    0.180215
 3    0.713647
 4    0.000022
 5    0.014779
 6    0.449904
 dtype: float64
In [510]: np.sqrt(ser)
Out[510]: 0    0.737160
 1    0.527607
 2    0.651550
 3    0.919117
 4    0.068694
 5    0.348668
 6    0.818993
 dtype: float64

序列的一个重要功能是根据标签自动对齐数据:

In [514]: ser[1:]
Out[514]: 1    0.278369
 2    0.424518
 3    0.844776
 4    0.004719
 5    0.121569
 6    0.670749
 dtype: float64
In [516]:ser[1:] + ser[:-2]
Out[516]: 0         NaN
 1    0.556739
 2    0.849035
 3    1.689552
 4    0.009438
 5         NaN
 6         NaN
 dtype: float64

因此,我们可以看到,对于不匹配的标签,插入了NaN。 默认行为是为未对齐的序列结构生成索引的并集。 这是可取的,因为信息可以保留而不是丢失。 在本书的下一章中,我们将处理 Pandas 中缺失的值。

数据帧

数据帧是一个二维标签数组。 它的列类型可以是异构的:即具有不同的类型。 它类似于 NumPy 中的结构化数组,并添加了可变性。 它具有以下属性:

  • 从概念上讲类似于数据表或电子表格。
  • 类似于 NumPy ndarray,但不是np.ndarray的子类。
  • 列可以是异构类型:float64intbool等。
  • 数据帧的列是序列结构。
  • 可以将其视为序列结构的字典,在该结构中,对列和行均进行索引,对于行,则表示为“索引”,对于列,则表示为“列”。
  • 它的大小可变:可以插入和删除列。

序列/数据帧中的每个轴都有索引,无论是否默认。 需要索引才能快速查找以及正确对齐和连接 Pandas 中的数据。 轴也可以命名,例如以月的形式表示列的数组 Jan Feb Mar ...Dec。这是索引数据帧的表示形式,其命名列的两端以及字符 V,W, X,Y,Z:

 columns nums strs bools decs 
 index 
 V            11       cat   True   1.4
 W            -6      hat   False  6.9 
 X             25     bat   False  -0.6
 Y               8     mat  True   3.7
 Z             -17    sat    False  18.

数据帧创建

数据帧是 Pandas 中最常用的数据结构。 构造器接受许多不同类型的参数:

  • 一维ndarray,列表,字典或序列结构的字典
  • 2D NumPy 数组
  • 结构化或记录ndarray
  • 序列结构
  • 另一个数据帧结构

行标签索引和列标签可以与数据一起指定。 如果未指定,则将以直观的方式从输入数据生成它们,例如,从dict.的键(对于列标签)或通过在行标签的情况下使用np.range(n)生成, 其中n对应于行数。

使用序列字典

在这里,我们通过使用序列对象的字典来创建数据帧结构。

In [97]:stockSummaries={
'AMZN': pd.Series([346.15,0.59,459,0.52,589.8,158.88], 
 index=['Closing price','EPS',
 'Shares Outstanding(M)',
 'Beta', 'P/E','Market Cap(B)']),
'GOOG': pd.Series([1133.43,36.05,335.83,0.87,31.44,380.64],
 index=['Closing price','EPS','Shares Outstanding(M)',
 'Beta','P/E','Market Cap(B)']),
'FB': pd.Series([61.48,0.59,2450,104.93,150.92], 
 index=['Closing price','EPS','Shares Outstanding(M)',
 'P/E', 'Market Cap(B)']),
'YHOO': pd.Series([34.90,1.27,1010,27.48,0.66,35.36],
 index=['Closing price','EPS','Shares Outstanding(M)',
 'P/E','Beta', 'Market Cap(B)']),
'TWTR':pd.Series([65.25,-0.3,555.2,36.23],
 index=['Closing price','EPS','Shares Outstanding(M)',
 'Market Cap(B)']), 
'AAPL':pd.Series([501.53,40.32,892.45,12.44,447.59,0.84],
 index=['Closing price','EPS','Shares Outstanding(M)','P/E',
 'Market Cap(B)','Beta'])}

In [99]: stockDF=pd.DataFrame(stockSummaries); stockDF
Out[99]:

AAPL AMZN FB GOOG TWTR YHOO
Beta 0.84 0.52 NaN 0.87 NaN 0.66
Closing Price 501.53 346.15 61.48 1133.43 65.25 34.9
EPS 40.32 0.59 0.59 36.05 -0.3 1.27
Market Cap(B) 447.59 158.88 150.92 380.64 36.23 35.36
P/E 12.44 589.8 104.93 31.44 NaN 27.48
Shares Outstanding(M) 892.45 459 2450 335.83 555.2 1010
In [100]:stockDF=pd.DataFrame(stockSummaries,
 index=['Closing price','EPS',
 'Shares Outstanding(M)',
 'P/E', 'Market Cap(B)','Beta']);stockDF
Out [100]:

AAPL AMZN FB GOOG TWTR YHOO
Closing price 501.53 346.15 61.48 1133.43 65.25 34.9
EPS 40.32 0.59 0.59 36.05 -0.3 1.27
Shares Outstanding(M) 892.45 459 2450 NaN 555.2 1010
P/E 12.44 589.8 104.93 31.44 NaN 27.48
Market Cap(B) 447.59 158.88 150.92 380.64 36.23 35.36
Beta 0.84 0.52 NaN 0.87 NaN 0.66
In [102]:stockDF=pd.DataFrame(stockSummaries,
 index=['Closing price','EPS','Shares Outstanding(M)',
 'P/E', 'Market Cap(B)','Beta'],
 columns=['FB','TWTR','SCNW'])
 stockDF
Out [102]:

FB TWTR SCNW
Closing price 61.48 65.25 NaN
EPS 0.59 -0.3 NaN
Shares Outstanding(M) 2450 555.2 NaN
P/E 104.93 NaN NaN
Market Cap(B) 150.92 36.23 NaN
Beta NaN NaN NaN

可以通过indexcolumns属性访问行索引标签和列标签:

In [527]: stockDF.index
Out[527]: Index([u'Closing price', u'EPS',
 u'Shares      Outstanding(M)', 
 u'P/E', u'Market Cap(B)', u'Beta'], dtype=object)
In [528]: stockDF.columns
Out[528]: Index([u'AAPL', u'AMZN', u'FB', u'GOOG', u'TWTR',
 u'YHOO'], dtype=object)

上述数据的来源是 Google 财经,2014 年 2 月 3 日访问。

使用ndarrays/列表字典

在这里,我们从列表的字典中创建一个数据帧结构。 键将成为数据帧结构中的列标签,列表中的数据将成为列值。 注意如何使用np.range(n)生成行标签索引。

In [529]:algos={'search':['DFS','BFS','Binary Search',
 'Linear','ShortestPath (Djikstra)'],
 'sorting': ['Quicksort','Mergesort', 'Heapsort',
 'Bubble Sort', 'Insertion Sort'],
 'machine learning':['RandomForest',
 'K Nearest Neighbor',
 'Logistic Regression',
 'K-Means Clustering',
 'Linear Regression']}
algoDF=pd.DataFrame(algos); algoDF
Out[529]: 
 machine learning  search   sorting
0   RandomForest   DFS   Quicksort
1   K Nearest Neighbor   BFS   Mergesort
2   Logistic Regression   Binary   Search   Heapsort
3   K-Means Clustering	 Linear   Bubble Sort
4   Linear Regression   ShortestPath (Djikstra)   Insertion Sort

In [530]: pd.DataFrame(algos,index=['algo_1','algo_2','algo_3','algo_4',
'algo_5'])
Out[530]: 
 machine learning  search     sorting
algo_1   RandomForest        DFS     Quicksort
algo_2   K Nearest Neighbor  BFS     Mergesort
algo_3   Logistic Regression  Binary Search Heapsort
algo_4   K-Means Clustering  Linear     Bubble Sort
algo_5   Linear Regression  ShortestPath (Djikstra) Insertion Sort

使用结构化数组

在这种情况下,我们使用结构化数组,即记录或structs的数组。 有关结构化数组的更多信息,请参考这个内容

In [533]: memberData = np.zeros((4,), 
 dtype=[('Name','a15'),
 ('Age','i4'),
 ('Weight','f4')])
 memberData[:] = [('Sanjeev',37,162.4),
 ('Yingluck',45,137.8),
 ('Emeka',28,153.2),
 ('Amy',67,101.3)]
 memberDF=pd.DataFrame(memberData);memberDF
Out[533]:         Name       Age    Weight
 0   Sanjeev    37  162.4
 1   Yingluck   45  137.8
 2   Emeka        28  153.2
 3   Amy        67  101.3
In [534]: pd.DataFrame(memberData, index=['a','b','c','d'])
Out[534]:    Name       Age    Weight
 a   Sanjeev    37  162.4
 b   Yingluck   45  137.8
 c   Emeka        28  153.2
 d   Amy        67  101.3

使用序列结构

在这里,我们展示如何从序列结构构造一个数据帧结构:

In [ 540]: currSeries.name='currency'
 pd.DataFrame(currSeries)
Out[540]:        currency
 China   yuan
 Germany euro
 Japan   yen
 Mexico   peso
 Nigeria naira
 UK   pound
 US   dollar

还有一些数据帧的替代构造器。 它们可以总结如下:

  • DataFrame.from_dict:它使用字典或序列的字典并返回数据帧。
  • DataFrame.from_records:需要一个元组或结构化ndarray的列表。
  • DataFrame.from_items:需要一些(键,值)对。 键是列或索引名,值是列或行值。 如果希望键为行索引名,则必须指定orient ='index'作为参数并指定列名。
  • pandas.io.parsers.read_csv:这是一个辅助函数,可将 CSV 文件读取到 Pandas 数据帧结构中。
  • pandas.io.parsers.read_table:这是一个辅助函数,它将定界文件读入 Pandas 数据帧结构。
  • pandas.io.parsers.read_fwf:这是一个辅助函数,它将固定宽度的线表读入 Pandas 数据帧结构。

操作

在这里,我将简要描述各种数据帧操作。

选取

特定的列可以作为序列结构获得:

In [543]: memberDF['Name']
Out[543]: 0    Sanjeev
 1    Yingluck
 2    Emeka
 3    Amy
 Name: Name, dtype: object

赋值

可以通过分配添加新列,如下所示:

In [545]:   memberDF['Height']=60;memberDF
Out[545]:          Name        Age  Weight  Height
 0                Sanjeev     37   162.4   60
 1                Yingluck    45   137.8   60
 2                Emeka       28   153.2   60
 3                Amy         67   101.3   60

删除

可以删除列,就像使用dict一样:

In [546]: del memberDF['Height']; memberDF
Out[546]:         Name       Age  Weight
 0               Sanjeev    37   162.4
 1               Yingluck   45   137.8
 2               Emeka      28   153.2
 3

Amy        67   101.3

也可以像字典一样弹出它:

In [547]: memberDF['BloodType']='O'
 bloodType=memberDF.pop('BloodType'); bloodType
Out[547]: 0    O
 1    O
 2    O
 3    O
 Name: BloodType, dtype: object

基本上,可以将数据帧结构看作是序列对象的字典。 列在末尾插入; 要在特定位置插入列,可以使用insert函数:

In [552]: memberDF.insert(2,'isSenior',memberDF['Age']>60);
 memberDF
Out[552]:      Name      Age  isSenior  Weight
 0            Sanjeev   37   False      162.4
 1            Yingluck  45   False     137.8
 2            Emeka     28   False     153.2
 3            Amy       67   True      101.3

对齐

数据帧对象以与序列对象相似的方式对齐,只不过它们在列和索引标签上都对齐。 结果对象是列标签和行标签的并集:

In [559]: ore1DF=pd.DataFrame(np.array([[20,35,25,20],
 [11,28,32,29]]),
 columns=['iron','magnesium',
 'copper','silver'])
 ore2DF=pd.DataFrame(np.array([[14,34,26,26],
 [33,19,25,23]]),
 columns=['iron','magnesium',
 'gold','silver'])
 ore1DF+ore2DF
Out[559]:     copper  gold  iron  magnesium  silver
 0           NaN     NaN   34    69         46
 1           NaN     NaN   44    47         52

在没有共同的行标签或列标签的情况下,该值用NaN填充,例如,铜和金。 如果将数据帧对象和序列对象组合在一起,则默认行为是在各行之间广播序列对象:

In [562]: ore1DF + pd.Series([25,25,25,25],
 index=['iron','magnesium',
 'copper','silver'])
Out[562]:    iron  magnesium   copper   silver
 0          45    60          50       45
 1          36    53          57       54

其他数学运算

可以将数学运算符明智地应用于数据帧结构:

In [565]: np.sqrt(ore1DF)
Out[565]:        iron       magnesium   copper         silver
 0              4.472136   5.916080    5.000000       4.472136
 1              3.316625   5.291503    5.656854       5.385165

面板

面板是 3D 数组。 它不如序列或数据帧广泛使用。 由于其 3D 性质,它不像其他两个屏幕那样容易在屏幕上显示或可视化。面板数据结构是 Pandas 中数据结构拼图的最后一部分。 它使用较少,用于 3D 数据。 三个轴名称如下:

  • item:这是轴 0。每个项目均对应一个数据帧结构。
  • major_axis:这是轴 1。每个项目对应于数据帧结构的行。
  • minor_axis:这是轴 2。每个项目对应于每个数据帧结构的列。

至于序列和数据帧,有创建面板对象的不同方法。 它们将在后面的章节中进行解释。

将 3D NumPy 数组与轴标签一起使用

在这里,我们展示了如何从 3D NumPy 数组构造面板对象。

In 586[]: stockData=np.array([[[63.03,61.48,75],
 [62.05,62.75,46],
 [62.74,62.19,53]],
 [[411.90, 404.38, 2.9],
 [405.45, 405.91, 2.6],
 [403.15, 404.42, 2.4]]])
 stockData
Out[586]: array([[[  63.03,   61.48,   75\.  ],
 [  62.05,   62.75,   46\.  ],
 [  62.74,   62.19,   53\.  ]],
 [[ 411.9 ,  404.38,    2.9 ],
 [ 405.45,  405.91,    2.6 ],
 [ 403.15,  404.42,    2.4 ]]])
In [587]: stockHistoricalPrices = pd.Panel(stockData, 
 items=['FB', 'NFLX'],
 major_axis=pd.date_range('2/3/2014', periods=3),
minor_axis=['open price', 'closing price', 'volume'])
 stockHistoricalPrices
Out[587]: <class 'pandas.core.panel.Panel'>
 Dimensions: 2 (items) x 3 (major_axis) x 3 (minor_axis)
 Items axis: FB to NFLX
 Major_axis axis: 2014-02-03 00:00:00 to 2014-02-05 00:00:00
 Minor_axis axis: open price to volume

使用数据帧对象的 Python 字典

我们通过使用数据帧结构的 Python 字典来构造面板结构。

In [591]: USData=pd.DataFrame(np.array([[249.62  , 8900],
 [ 282.16,12680],
 [309.35,14940]]),
 columns=['Population(M)','GDP($B)'], 
 index=[1990,2000,2010])
 USData
Out[591]:       Population(M)   GDP($B)
 1990    249.62          8900
 2000    282.16          12680
 2010    309.35          14940
In [590]: ChinaData=pd.DataFrame(np.array([[1133.68, 390.28],
 [ 1266.83,1198.48],
 [1339.72, 6988.47]]),

 columns=['Population(M)','GDP($B)'],
 index=[1990,2000,2010])
 ChinaData
Out[590]:          Population(M)   GDP($B)
 1990    1133.68         390.28
 2000    1266.83         1198.48
 2010    1339.72         6988.47
In [592]:US_ChinaData={'US' : USData,
 'China': ChinaData}
 pd.Panel(US_ChinaData)
Out[592]: <class 'pandas.core.panel.Panel'>
 Dimensions: 2 (items) x 3 (major_axis) x 2 (minor_axis)
 Items axis: China to US
 Major_axis axis: 1990 to 2010

使用DataFrame.to_panel方法

此方法将具有多重索引的数据帧结构转换为面板结构:

In [617]: mIdx = pd.MultiIndex(levels=[['US', 'China'], 
 [1990,2000, 2010]],
 labels=[[1,1,1,0,0,0],[0,1,2,0,1,2]])
mIdx
Out[617]: MultiIndex
 [(u'China', 1990), (u'China', 2000), (u'China', 2010), 
 (u'US', 1990), (u'US', 2000), (u'US', 2010)]

ChinaUSDF = pd.DataFrame({'Population(M)' : [1133.68, 1266.83, 
 1339.72, 249.62, 
 282.16,309.35], 
 'GDB($B)': [390.28, 1198.48, 6988.47, 
 8900,12680, 14940]}, index=mIdx)
ChinaUSDF
In [618]: ChinaUSDF = pd.DataFrame({'Population(M)' : [1133.68, 
 1266.83, 
 1339.72, 
 249.62, 
 282.16,
 309.35], 
 'GDB($B)': [390.28, 1198.48, 
 6988.47, 8900,
 12680,14940]}, 
 index=mIdx)
 ChinaUSDF

Out[618]:                       GDB($B)       Population(M)
 China       1990     390.28        1133.68
 2000     1198.48       1266.83
 2010     6988.47       1339.72
 US          1990     8900.00        249.62
 2000     12680.00       282.16
 2010     14940.00       309.35
In [622]: ChinaUSDF.to_panel()
Out[622]: <class 'pandas.core.panel.Panel'>
 Dimensions: 2 (items) x 2 (major_axis) x 3 (minor_axis)
 Items axis: GDB($B) to Population(M)
 Major_axis axis: US to China
 Minor_axis axis: 1990 to 2010

美国/中国经济数据的来源是以下站点:

其他操作

插入,删除和逐项操作的行为与数据帧相同。 面板结构可以通过转置重新排列。面板的操作功能集相对欠发达,不如序列和数据帧丰富。

总结

总结本章,numpy.ndarray是 Pandas 数据结构所基于的基岩数据结构。 Pandas 的数据结构由 NumPy ndarray数据和一个或多个标签数组组成。

Pandas 中有三种主要的数据结构:序列,数据帧架和面板。 与 Numpy ndarrays相比,pandas 数据结构更易于使用且更加用户友好,因为在数据帧和面板的情况下,它们提供行索引和列索引。数据帧对象是 Pandas 中最流行和使用最广泛的对象。 在下一章中,我们将讨论 Pandas 索引的主题。

四、Pandas 的操作,第一部分 -- 索引和选择

在本章中,我们将着重于对来自 Pandas 对象的数据进行索引和选择。 这很重要,因为有效利用 Pandas 需要对索引和选择数据有充分的了解。 我们将在本章中讨论的主题包括:

  • 基本索引
  • 标签,整数和混合索引
  • 多重索引
  • 布尔索引
  • 索引操作

基本索引

在上一章中,我们已经讨论了有关序列和数据帧的基本索引,但是为了完整起见,这里我们将包括一些示例。 在这里,我们列出了根据 IMF 数据得出的 2013 年第 4 季度原油现货价格的时间序列。

In [642]:SpotCrudePrices_2013_Data={
 'U.K. Brent' : {'2013-Q1':112.9, '2013-Q2':103.0, '2013-Q3':110.1, '2013-Q4':109.4},
 'Dubai':{'2013-Q1':108.1, '2013-Q2':100.8, '2013-Q3':106.1,'2013-Q4':106.7},
 'West Texas Intermediate':{'2013-Q1':94.4, '2013-Q2':94.2, '2013-Q3':105.8,'2013-Q4':97.4}}

 SpotCrudePrices_2013=pd.DataFrame.from_dict(SpotCrudePrices_2013_Data)
 SpotCrudePrices_2013
Out[642]:        Dubai   U.K. Brent  West Texas Intermediate
 2013-Q1  108.1   112.9          94.4
 2013-Q2  100.8   103.0          94.2
 2013-Q3  106.1   110.1          105.8
 2013-Q4  106.7   109.4          97.4

我们可以使用[]运算符为迪拜原油的可用时间段选择价格:

In [644]: dubaiPrices=SpotCrudePrices_2013['Dubai']; dubaiPrices
Out[644]: 2013-Q1    108.1
 2013-Q2    100.8
 2013-Q3    106.1
 2013-Q4    106.7
 Name: Dubai, dtype: float64

我们可以将列列表传递给[]运算符,以便以特定顺序选择列:

In [647]: SpotCrudePrices_2013[['West Texas Intermediate','U.K. Brent']]
Out[647]:          West Texas Intermediate        U.K. Brent
 2013-Q1                      94.4        112.9
 2013-Q2                      94.2        103.0
 2013-Q3                     105.8        110.1
 2013-Q4                      97.4        109.4

如果我们指定未在数据帧中列出的列,则将出现KeyError异常:

In [649]: SpotCrudePrices_2013['Brent Blend']
 --------------------------------------------------------
 KeyError                                  Traceback (most recent call last)
 <ipython-input-649-cd2d76b24875> in <module>()
 ...
 KeyError: u'no item named Brent Blend'

我们可以通过使用get运算符并在不存在该列的情况下指定默认值来避免此错误,如下所示:

In [650]: SpotCrudePrices_2013.get('Brent Blend','N/A')
Out[650]: 'N/A'

注意

请注意,无法使用数据帧中的括号运算符[]选择行。

因此,在以下情况下会出现错误:

In [755]:SpotCrudePrices_2013['2013-Q1']
 --------------------------------------------------
 KeyError        Traceback (most recent call last)
 ...
 KeyError: u'no item named 2013-Q1'

这是创作者为避免歧义而做出的设计决定。 对于序列,没有歧义,可以使用[]运算符选择行:

In [756]: dubaiPrices['2013-Q1']
Out[756]: 108.1

我们将在本章后面看到如何使用一种较新的索引运算符执行行选择。

使用点运算符访问属性

可以直接从序列,数据帧或面板中检索值作为属性,如下所示:

In [650]: SpotCrudePrices_2013.Dubai
Out[650]: 2013-Q1    108.1
 2013-Q2    100.8
 2013-Q3    106.1
 2013-Q4    106.7
 Name: Dubai, dtype: float64

但是,这仅在索引元素是有效的 Python 标识符时才有效,如下所示:

In [653]: SpotCrudePrices_2013."West Texas Intermediate"
 File "<ipython-input-653-2a782563c15a>", line 1
 SpotCrudePrices_2013."West Texas Intermediate"
 ^
 SyntaxError: invalid syntax

否则,由于列名中的空格,我们将像前面的情况一样获得SyntaxError。 有效的 Python 标识符必须遵循以下词汇约定:

identifier::= (letter|"_") (letter | digit | "_")*

因此,有效的 Python 标识符不能包含空格。 有关更多详细信息,请参见 Python 词法分析文档

我们可以通过重命名列索引名称来解决这些问题,以便它们都是有效的标识符:

In [654]: SpotCrudePrices_2013
Out[654]:               Dubai    U.K. Brent     West Texas Intermediate
 2013-Q1        108.1    112.9          94.4
 2013-Q2        100.8    103.0          94.2
 2013-Q3        106.1    110.1          105.8
 2013-Q4        106.7    109.4          97.4

In [655]:SpotCrudePrices_2013.columns=['Dubai','UK_Brent', 
 'West_Texas_Intermediate']
SpotCrudePrices_2013
Out[655]:         Dubai    UK_Brent       West_Texas_Intermediate
 2013-Q1  108.1    112.9          94.4
 2013-Q2  100.8    103.0          94.2
 2013-Q3  106.1    110.1          105.8
 2013-Q4  106.7    109.4          97.4

然后,我们可以根据需要选择 West Texas Intermediate 的价格:

In [656]:SpotCrudePrices_2013.West_Texas_Intermediate
Out[656]:2013-Q1     94.4
 2013-Q2     94.2
 2013-Q3    105.8
 2013-Q4     97.4
 Name: West_Texas_Intermediate, dtype: float64

我们还可以通过指定列索引号以选择第 1 列(英国布伦特)来选择价格,如下所示:

In [18]: SpotCrudePrices_2013[[1]]
Out[18]:        U.K. Brent
 2013-Q1  112.9
 2013-Q2  103.0
 2013-Q3  110.1
 2013-Q4  109.4

范围切片

正如我们在第 3 章“pandas 数据结构”中有关 NumPy ndarray的部分中所看到的那样,我们可以使用[]运算符对范围进行切片。 切片运算符的语法与 NumPy 的语法完全匹配:

ar[startIndex: endIndex: stepValue]

如果未指定,则默认值如下:

  • startIndex为 0
  • endIndexarraysize - 1
  • stepValue为 1

对于数据帧,[]跨行切片如下:

获取前两行:

In [675]: SpotCrudePrices_2013[:2]
Out[675]:        Dubai  UK_Brent West_Texas_Intermediate
 2013-Q1  108.1   112.9   94.4
 2013-Q2  100.8   103.0   94.2

获取从索引 2 开始的所有行:

In [662]: SpotCrudePrices_2013[2:]
Out[662]:        Dubai  UK_Brent West_Texas_Intermediate
 2013-Q3  106.1   110.1   105.8
 2013-Q4  106.7   109.4   97.4

从第 0 行开始,以两间隔获取行:

In [664]: SpotCrudePrices_2013[::2]
Out[664]:        Dubai  UK_Brent West_Texas_Intermediate
 2013-Q1  108.1   112.9   94.4
 2013-Q3  106.1   110.1   105.8

反转数据帧中的行顺序:

In [677]: SpotCrudePrices_2013[::-1]
Out[677]:        Dubai  UK_Brent West_Texas_Intermediate
 2013-Q4  106.7   109.4   97.4
 2013-Q3  106.1   110.1   105.8
 2013-Q2  100.8   103.0   94.2
 2013-Q1  108.1   112.9   94.4

对于序列,其行为也很直观:

In [666]: dubaiPrices=SpotCrudePrices_2013['Dubai']

获取最后三行或除第一行外的所有行:

In [681]: dubaiPrices[1:]
Out[681]: 2013-Q2    100.8
 2013-Q3    106.1
 2013-Q4    106.7
 Name: Dubai, dtype: float64

获取除最后一行以外的所有行:

In [682]: dubaiPrices[:-1]
Out[682]: 2013-Q1    108.1
 2013-Q2    100.8
 2013-Q3    106.1
 Name: Dubai, dtype: float64

反转行:

In [683]: dubaiPrices[::-1]
Out[683]: 2013-Q4    106.7
 2013-Q3    106.1
 2013-Q2    100.8
 2013-Q1    108.1
 Name: Dubai, dtype: float64

标签,整数和混合索引

除了标准索引运算符[]和属性运算符外,pandas 中还提供了一些运算符,以使索引工作更轻松,更方便。 通过标签索引,我们通常是指通过标题名称进行索引,该标题名称在大多数情况下往往是字符串值。 这些运算符如下:

  • .loc运算符:它允许基于标签的索引
  • .iloc运算符:它允许基于整数的索引
  • .ix运算符:它允许混合基于标签和整数的索引

现在,我们将注意力转向这些运算符。

面向标签的索引

.loc运算符支持基于纯标签的索引。 它接受以下内容作为有效输入:

  • 单个标签,例如['March'][88]['Dubai']。 请注意,在标签是整数的情况下,它不是引用索引的整数位置,而是引用整数本身作为标签。
  • 标签列表或数组,例如['Dubai', 'UK Brent']
  • 带标签的切片对象,例如'May':'Aug'
  • 布尔数组。

对于我们的说明性数据集,我们使用以下城市的平均下雪天气温度数据:

创建数据帧

In [723]: NYC_SnowAvgsData={'Months' :
['January','February','March', 
'April', 'November', 'December'],
'Avg SnowDays' : [4.0,2.7,1.7,0.2,0.2,2.3],
'Avg Precip. (cm)' : [17.8,22.4,9.1,1.5,0.8,12.2],
'Avg Low Temp. (F)' : [27,29,35,45,42,32] }
In [724]: NYC_SnowAvgsData
Out[724]:{'Avg Low Temp. (F)': [27, 29, 35, 45, 42, 32],
 'Avg Precip. (cm)': [17.8, 22.4, 9.1, 1.5, 0.8, 12.2],
 'Avg SnowDays': [4.0, 2.7, 1.7, 0.2, 0.2, 2.3],
 'Months': ['January', 'February', 'March', 'April', 
 'November', 'December']}

In [726]:NYC_SnowAvgs=pd.DataFrame(NYC_SnowAvgsData, 
 index=NYC_SnowAvgsData['Months'], 
 columns=['Avg SnowDays','Avg Precip. (cm)', 
 'Avg Low Temp. (F)'])
 NYC_SnowAvgs

Out[726]:        Avg SnowDays   Avg Precip. (cm) Avg Low Temp. (F)
 January  4.0            17.8             27
 February 2.7            22.4             29
 March    1.7            9.1              35
 April    0.2            1.5              45
 November 0.2            0.8              42
 December 2.3            12.2             32

使用单个标签:

In [728]: NYC_SnowAvgs.loc['January']
Out[728]: Avg SnowDays          4.0
 Avg Precip. (cm)     17.8
 Avg Low Temp. (F)    27.0
 Name: January, dtype: float64

使用标签列表:

In [730]: NYC_SnowAvgs.loc[['January','April']]
Out[730]:        Avg SnowDays   Avg Precip. (cm) Avg Low Temp. (F)
 January  4.0            17.8             27
 April    0.2            1.5              45

使用标签范围:

In [731]: NYC_SnowAvgs.loc['January':'March']
Out[731]:        Avg SnowDays   Avg Precip. (cm) Avg Low Temp. (F)
 January  4.0            17.8             27
 February 2.7            22.4             29
 March    1.7            9.1              35

请注意,在数据帧上使用.loc.iloc.ix运算符时,必须始终首先指定行索引。 这与[]运算符相反,后者只能直接选择列。 因此,如果执行以下操作,则会出现错误:

In [771]: NYC_SnowAvgs.loc['Avg SnowDays']

KeyError: 'Avg SnowDays'

正确的方法是使用冒号(:)运算符专门选择所有行,如下所示:

In [772]: NYC_SnowAvgs.loc[:,'Avg SnowDays']
Out[772]: January     4.0
 February    2.7
 March       1.7
 April       0.2
 November    0.2
 December    2.3
 Name: Avg SnowDays, dtype: float64

在这里,我们看到如何选择一个特定的坐标值,即三月份的平均下雪天数:

In [732]: NYC_SnowAvgs.loc['March','Avg SnowDays']
Out[732]: 1.7

还支持这种替代样式:

In [733]: NYC_SnowAvgs.loc['March']['Avg SnowDays']
Out[733]: 1.7

以下是使用方括号运算符[]的前述情况的等效内容:

In [750]: NYC_SnowAvgs['Avg SnowDays']['March']
Out[750]: 1.7

再次注意,但是,首先使用.loc运算符指定行索引值将得到Keyerror。 这是前面讨论的事实的结果,即[]运算符不能用于直接选择行。 必须首先选择列以获得序列,然后可以按行选择。 因此,如果使用以下任一方法,则将获得KeyError: u'no item named March'

In [757]: NYC_SnowAvgs['March']['Avg SnowDays']

要么

In [758]: NYC_SnowAvgs['March']

我们可以使用.loc运算符来选择行:

In [759]: NYC_SnowAvgs.loc['March']
Out[759]: Avg SnowDays          1.7
 Avg Precip. (cm)      9.1
 Avg Low Temp. (F)    35.0
 Name: March, dtype: float64

使用布尔数组进行选择

现在,我们将展示如何使用布尔数组选择平均下雪天少于一个的月份:

In [763]: NYC_SnowAvgs.loc[NYC_SnowAvgs['Avg SnowDays']<1,:]
Out[763]:         Avg SnowDays  Avg Precip. (cm) Avg Low Temp. (F)
 April     0.2           1.5              45
 November  0.2           0.8              42

或者,对于前面提到的现货原油价格,在2013-Q1行中,选择与价格高于每桶 110 美元的原油品牌相对应的列:

In [768]: SpotCrudePrices_2013.loc[:,SpotCrudePrices_2013.loc['2013-Q1']>110]
Out[768]:        UK_Brent
 2013-Q1 112.9
 2013-Q2 103.0
 2013-Q3 110.1
 2013-Q4 109.4

请注意,前面的参数涉及实际计算布尔数组的布尔运算符<>,例如:

In [769]: SpotCrudePrices_2013.loc['2013-Q1']>110
Out[769]: Dubai                    False
 UK_Brent                 True
 West_Texas_Intermediate  False
 Name: 2013-Q1, dtype: bool

面向整数的索引

.iloc运算符支持基于整数的位置索引。 它接受以下内容作为输入:

  • 一个整数,例如 7
  • 整数列表或数组,例如[2, 3]
  • 具有整数的切片对象,例如1:4

让我们创建以下内容:

In [777]: import scipy.constants as phys
 import math
In [782]: sci_values=pd.DataFrame([[math.pi, math.sin(math.pi), 
 math.cos(math.pi)],
 [math.e,math.log(math.e), 
 phys.golden],
 [phys.c,phys.g,phys.e],
 [phys.m_e,phys.m_p,phys.m_n]],
 index=list(range(0,20,5)))

Out[782]:             0               1               2
 0     3.141593e+00    1.224647e-16   -1.000000e+00
 5     2.718282e+00    1.000000e+00    1.618034e+00
 10    2.997925e+08    9.806650e+00    1.602177e-19
 15    9.109383e-31    1.672622e-27    1.674927e-27

我们可以使用整数切片在前两行中选择非物理常数:

In [789]: sci_values.iloc[:2]
Out[789]:               0       1        2
 0        3.141593  1.224647e-16 -1.000000
 5        2.718282  1.000000e+00  1.618034

或者,我们可以在第三行中使用光速和重力加速度:

In [795]: sci_values.iloc[2,0:2]
Out[795]: 0    2.997925e+08
 1    9.806650e+00
 dtype: float64

请注意,.iloc的参数严格位于位置,与索引值无关。 因此,请考虑以下情况,我们错误地认为可以通过使用以下命令获得第三行:

In [796]: sci_values.iloc[10]
 ------------------------------------------------------
 IndexError                                Traceback (most recent call last)
 ...
 IndexError: index 10 is out of bounds for axis 0 with size 4

在这里,我们得到前面结果中的IndexError; 因此,现在,我们应改为使用标签索引运算符.loc,如下所示:

In [797]: sci_values.loc[10]
Out[797]: 0    2.997925e+08
 1    9.806650e+00
 2    1.602177e-19
 Name: 10, dtype: float64

要切出特定的行,我们可以使用以下命令:

In [802]: sci_values.iloc[2:3,:]
Out[802]:     0          1       2
 10   299792458  9.80665 1.602177e-19

要使用整数位置获取横截面,请使用以下命令:

In [803]: sci_values.iloc[3]
Out[803]: 0    9.109383e-31
 1    1.672622e-27
 2    1.674927e-27
 Name: 15, dtype: float64

如果我们尝试切片超过数组的末尾,则我们将获得IndexError,如下所示:

In [805]: sci_values.iloc[6,:]
 --------------------------------------------------------
 IndexError                                Traceback (most recent call last)
 IndexError: index 6 is out of bounds for axis 0 with size 4

.iat.at运算符

.iat.at运算符可用于快速选择标量值。 最好的说明如下:

In [806]: sci_values.iloc[3,0]
Out[806]: 9.1093829099999999e-31
In [807]: sci_values.iat[3,0]
Out[807]: 9.1093829099999999e-31

In [808]: %timeit sci_values.iloc[3,0]
 10000 loops, best of 3: 122 μs per loop
In [809]: %timeit sci_values.iat[3,0]
 10000 loops, best of 3: 28.4 μs per loop

因此,我们可以看到.iat.iloc / .ix运算符快得多。 .at.loc的情况相同。

.ix运算符的混合索引

.ix运算符的行为类似于.loc.iloc运算符的混合,其中.loc行为优先。 它采用以下作为可能的输入:

  • 单个标签或整数
  • 整数或标签列表
  • 整数切片或标签切片
  • 布尔数组

让我们通过将股票指数收盘价数据保存到文件(stock_index_closing.csv)并将其读取来重新创建以下数据帧:

TradingDate,Nasdaq,S&P 500,Russell 2000
2014/01/30,4123.13,1794.19,1139.36
2014/01/31,4103.88,1782.59,1130.88
2014/02/03,3996.96,1741.89,1094.58
2014/02/04,4031.52,1755.2,1102.84
2014/02/05,4011.55,1751.64,1093.59
2014/02/06,4057.12,1773.43,1103.93

该数据的来源是这里。 这是我们将 CSV 数据读入数据帧的方法:

In [939]: stockIndexDataDF=pd.read_csv('./stock_index_data.csv')
In [940]: stockIndexDataDF
Out[940]:        TradingDate  Nasdaq   S&P 500  Russell 2000
 0        2014/01/30   4123.13  1794.19  1139.36
 1        2014/01/31   4103.88  1782.59  1130.88
 2        2014/02/03   3996.96  1741.89  1094.58
 3        2014/02/04   4031.52  1755.20  1102.84
 4        2014/02/05   4011.55  1751.64  1093.59
 5        2014/02/06   4057.12  1773.43  1103.93

从前面的示例中可以看到,创建的数据帧具有基于整数的行索引。 我们立即将索引设置为交易日期,以便根据交易日期对其进行索引,以便可以使用.ix运算符:

In [941]: stockIndexDF=stockIndexDataDF.set_index('TradingDate')
In [942]:stockIndexDF
Out[942]:               Nasdaq   S&P 500  Russell 2000
 TradingDate 
 2014/01/30      4123.13  1794.19  1139.36
 2014/01/31      4103.88  1782.59  1130.88
 2014/02/03      3996.96  1741.89  1094.58
 2014/02/04      4031.52  1755.20  1102.84
 2014/02/05      4011.55  1751.64  1093.59
 2014/02/06      4057.12  1773.43  1103.93

现在,我们展示使用.ix运算符的示例:

Using a single label:

In [927]: stockIndexDF.ix['2014/01/30']
Out[927]: Nasdaq          4123.13
 S&P 500         1794.19
 Russell 2000    1139.36
 Name: 2014/01/30, dtype: float64

Using a list of labels:

In [928]: stockIndexDF.ix[['2014/01/30']]
Out[928]:           Nasdaq   S&P 500  Russell 2000
 2014/01/30  4123.13  1794.19  1139.36

In [930]: stockIndexDF.ix[['2014/01/30','2014/01/31']]
Out[930]:           Nasdaq  S&P 500  Russell 2000
 2014/01/30  4123.13    1794.19  1139.36
 2014/01/31  4103.88    1782.59  1130.88

请注意,使用单个标签与使用仅包含单个标签的列表之间的输出差异。 前者产生序列,而后者产生一个数据帧:

In [943]: type(stockIndexDF.ix['2014/01/30'])
Out[943]: pandas.core.series.Series

In [944]: type(stockIndexDF.ix[['2014/01/30']])
Out[944]: pandas.core.frame.DataFrame

对于前者,索引器是一个标量; 对于后者,索引器是一个列表。 列表索引器用于选择多个列。 一个数据帧的多列切片只能生成另一个数据帧,因为它是 2D 的。 因此,在后一种情况下返回的是一个数据帧。
使用基于标签的切片:

In [932]: tradingDates=stockIndexDataDF.TradingDate
In [934]: stockIndexDF.ix[tradingDates[:3]]
Out[934]:               Nasdaq    S&P 500  Russell 2000
 2014/01/30       4123.13  1794.19  1139.36
 2014/01/31       4103.88  1782.59  1130.88
 2014/02/03       3996.96  1741.89  1094.58

使用单个整数:

In [936]: stockIndexDF.ix[0]
Out[936]: Nasdaq          4123.13
 S&P 500         1794.19
 Russell 2000    1139.36
 Name: 2014/01/30, dtype: float64

使用整数列表:

In [938]: stockIndexDF.ix[[0,2]]
Out[938]:               Nasdaq   S&P 500  Russell 2000
 TradingDate 
 2014/01/30      4123.13  1794.19  1139.36
 2014/02/03      3996.96  1741.89  1094.58

使用整数切片:

In [947]: stockIndexDF.ix[1:3]
Out[947]:               Nasdaq   S&P 500  Russell 2000
 TradingDate 
 2014/01/31      4103.88  1782.59  1130.88
 2014/02/03      3996.96  1741.89  1094.58

使用布尔数组:

In [949]: stockIndexDF.ix[stockIndexDF['Russell 2000']>1100]
Out[949]:               Nasdaq  S&P 500  Russell 2000
 TradingDate 
 2014/01/30      4123.13 1794.19  1139.36
 2014/01/31      4103.88 1782.59  1130.88
 2014/02/04      4031.52 1755.20  1102.84
 2014/02/06      4057.12 1773.43  1103.93

.loc的情况一样,必须首先为.ix运算符指定行索引。

多重索引

现在我们转到多重索引的主题。 多级或分层索引很有用,因为它使 Pandas 用户可以使用序列和数据帧等数据结构来选择和按摩多维数据。 为了开始,让我们将以下数据保存到文件中:stock_index_prices.csv并读入:

TradingDate,PriceType,Nasdaq,S&P 500,Russell 2000
2014/02/21,open,4282.17,1841.07,1166.25
2014/02/21,close,4263.41,1836.25,1164.63
2014/02/21,high,4284.85,1846.13,1168.43
2014/02/24,open,4273.32,1836.78,1166.74
2014/02/24,close,4292.97,1847.61,1174.55
2014/02/24,high,4311.13,1858.71,1180.29
2014/02/25,open,4298.48,1847.66,1176
2014/02/25,close,4287.59,1845.12,1173.95
2014/02/25,high,4307.51,1852.91,1179.43
2014/02/26,open,4300.45,1845.79,1176.11
2014/02/26,close,4292.06,1845.16,1181.72
2014/02/26,high,4316.82,1852.65,1188.06
2014/02/27,open,4291.47,1844.9,1179.28
2014/02/27,close,4318.93,1854.29,1187.94
2014/02/27,high,4322.46,1854.53,1187.94
2014/02/28,open,4323.52,1855.12,1189.19
2014/02/28,close,4308.12,1859.45,1183.03
2014/02/28,high,4342.59,1867.92,1193.5

In [950]:sharesIndexDataDF=pd.read_csv('./stock_index_prices.csv')
In [951]: sharesIndexDataDF
Out[951]:
 TradingDate  PriceType  Nasdaq     S&P 500  Russell 2000
0   2014/02/21   open     4282.17  1841.07  1166.25
1   2014/02/21   close     4263.41  1836.25  1164.63
2   2014/02/21   high     4284.85  1846.13  1168.43
3   2014/02/24   open     4273.32  1836.78  1166.74
4   2014/02/24   close     4292.97  1847.61  1174.55
5   2014/02/24   high      4311.13  1858.71  1180.29
6   2014/02/25   open     4298.48  1847.66  1176.00
7   2014/02/25   close     4287.59  1845.12  1173.95
8   2014/02/25   high     4307.51  1852.91  1179.43
9   2014/02/26   open     4300.45  1845.79  1176.11
10   2014/02/26   close     4292.06  1845.16  1181.72
11   2014/02/26   high     4316.82  1852.65  1188.06
12   2014/02/27   open     4291.47  1844.90  1179.28
13   2014/02/27   close     4318.93  1854.29  1187.94
14   2014/02/27   high     4322.46  1854.53  1187.94
15   2014/02/28   open     4323.52  1855.12 1189.19
16   2014/02/28   close     4308.12  1859.45 1183.03
17   2014/02/28   high     4342.59  1867.92 1193.50

在这里,我们从交易日期和priceType列创建一个多重索引:

In [958]: sharesIndexDF=sharesIndexDataDF.set_index(['TradingDate','PriceType'])
In [959]: mIndex=sharesIndexDF.index; mIndex
Out[959]: MultiIndex
 [(u'2014/02/21', u'open'), (u'2014/02/21', u'close'), (u'2014/02/21', u'high'), (u'2014/02/24', u'open'), (u'2014/02/24', u'close'), (u'2014/02/24', u'high'), (u'2014/02/25', u'open'), (u'2014/02/25', u'close'), (u'2014/02/25', u'high'), (u'2014/02/26', u'open'), (u'2014/02/26', u'close'), (u'2014/02/26', u'high'), (u'2014/02/27', u'open'), (u'2014/02/27', u'close'), (u'2014/02/27', u'high'), (u'2014/02/28', u'open'), (u'2014/02/28', u'close'), (u'2014/02/28', u'high')]

In [960]: sharesIndexDF
Out[960]:              Nasdaq  S&P 500   Russell 2000
TradingDate PriceType
2014/02/21  open    4282.17  1841.07  1166.25
 close   4263.41  1836.25  1164.63
 high    4284.85  1846.13  1168.43
2014/02/24  open    4273.32  1836.78  1166.74
 close   4292.97  1847.61  1174.55
 high    4311.13  1858.71  1180.29
2014/02/25  open    4298.48  1847.66  1176.00
 close   4287.59  1845.12  1173.95
 high    4307.51  1852.91  1179.43
2014/02/26  open    4300.45  1845.79  1176.11
 close   4292.06  1845.16  1181.72
 high    4316.82  1852.65  1188.06
2014/02/27  open    4291.47  1844.90  1179.28
 close   4318.93  1854.29  1187.94
 high    4322.46  1854.53  1187.94
2014/02/28  open    4323.52  1855.12  1189.19
 close   4308.12  1859.45  1183.03
 high    4342.59  1867.92  1193.50

经过检查,我们发现多重索引包含一个元组列表。 将get_level_values函数与适当的参数一起应用将为索引的每个级别生成标签列表:

In [962]: mIndex.get_level_values(0)
Out[962]: Index([u'2014/02/21', u'2014/02/21', u'2014/02/21', u'2014/02/24', u'2014/02/24', u'2014/02/24', u'2014/02/25', u'2014/02/25', u'2014/02/25', u'2014/02/26', u'2014/02/26', u'2014/02/26', u'2014/02/27', u'2014/02/27', u'2014/02/27', u'2014/02/28', u'2014/02/28', u'2014/02/28'], dtype=object)

In [963]: mIndex.get_level_values(1)
Out[963]: Index([u'open', u'close', u'high', u'open', u'close', u'high', u'open', u'close', u'high', u'open', u'close', u'high', u'open', u'close', u'high', u'open', u'close', u'high'], dtype=object)

但是,如果传递给get_level_values()的值无效或超出范围,则会抛出IndexError

In [88]: mIndex.get_level_values(2)
 ---------------------------------------------------------
IndexError                      Traceback (most recent call last)
...

您可以使用多重索引的数据帧实现分层索引:

In [971]: sharesIndexDF.ix['2014/02/21']
Out[971]:       Nasdaq   S&P 500	Russell 2000
 PriceType
 open       4282.17  1841.07  1166.25
 close       4263.41  1836.25  1164.63
 high       4284.85  1846.13  1168.43

In [976]: sharesIndexDF.ix['2014/02/21','open']
Out[976]: Nasdaq          4282.17
 S&P 500         1841.07
 Russell 2000    1166.25
 Name: (2014/02/21, open), dtype: float64 

我们可以使用多重索引进行切片:

In [980]: sharesIndexDF.ix['2014/02/21':'2014/02/24']
Out[980]:      Nasdaq   S&P 500   Russell 2000
 TradingDate  PriceType
 2014/02/21   open  4282.17   1841.07   1166.25
 close  4263.41   1836.25   1164.63
 high  4284.85   1846.13   1168.43
 2014/02/24   open  4273.32   1836.78   1166.74
 close  4292.97   1847.61   1174.55
 high  4311.13   1858.71   1180.29

我们可以尝试在较低级别进行切片:

In [272]:
sharesIndexDF.ix[('2014/02/21','open'):('2014/02/24','open')]
------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-272-65bb3364d980> in <module>()
----> 1 sharesIndexDF.ix[('2014/02/21','open'):('2014/02/24','open')]
...
KeyError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'

但是,这会导致KeyError出现非常奇怪的错误消息。 这里要学习的关键知识是,多重索引的当前版本要求对标签进行排序,以使较低级别的切片例程正常工作。

为此,您可以利用sortlevel()方法对多重索引中的轴的标签进行排序。 为了安全起见,在使用多重索引切片之前,请先进行排序。 因此,我们可以执行以下操作:

In [984]: sharesIndexDF.sortlevel(0).ix[('2014/02/21','open'):('2014/02/24','open')]
Out[984]:          Nasdaq    S&P 500  Russell 2000
 TradingDate  PriceType
 2014/02/21   open      4282.17   1841.07   1166.25
 2014/02/24   close     4292.97   1847.61   1174.55
 high      4311.13   1858.71   1180.29
 open      4273.32   1836.78   1166.74

我们还可以传递一个元组列表:

In [985]: sharesIndexDF.ix[[('2014/02/21','close'),('2014/02/24','open')]]
Out[985]:      Nasdaq  S&P 500  Russell 2000
 TradingDate  PriceType
 2014/02/21   close  4263.41  1836.25  1164.63
 2014/02/24   open  4273.32  1836.78  1166.74
 2 rows × 3 columns

请注意,通过指定一个元组列表,而不是前面的示例中的范围,我们仅显示打开的PriceType的值,而不显示TradingDate 2014/02/24的全部三个值。

交换和重新排序级别

swaplevel函数可在多重索引中交换级别:

In [281]: swappedDF=sharesIndexDF[:7].swaplevel(0, 1, axis=0)
 swappedDF
Out[281]:        Nasdaq    S&P 500  Russell 2000
 PriceType  TradingDate
 open	     2014/02/21   4282.17  1841.07  1166.25
 close    2014/02/21    4263.41  1836.25  1164.63
 high     2014/02/21    4284.85  1846.13  1168.43
 open     2014/02/24    4273.32  1836.78  1166.74
 close    2014/02/24    4292.97  1847.61  1174.55
 high     2014/02/24    4311.13  1858.71  1180.29
 open	    2014/02/25    4298.48  1847.66  1176.00
 7 rows × 3 columns

reorder_levels函数更通用,允许您指定级别的顺序:

In [285]: reorderedDF=sharesIndexDF[:7].reorder_levels(['PriceType',
 'TradingDate'],
 axis=0)
 reorderedDF
Out[285]:        Nasdaq    S&P 500  Russell 2000
 PriceType  TradingDate
 open     2014/02/21   4282.17  1841.07  1166.25
 close    2014/02/21   4263.41  1836.25  1164.63
 high     2014/02/21   4284.85  1846.13  1168.43
 open     2014/02/24   4273.32  1836.78  1166.74
 close    2014/02/24   4292.97  1847.61  1174.55
 high     2014/02/24   4311.13  1858.71  1180.29
 open     2014/02/25   4298.48  1847.66  1176.00
 7 rows × 3 columns

交叉选择

xs方法提供了一种基于特定索引级别值选择数据的快捷方式:

In [287]: sharesIndexDF.xs('open',level='PriceType')
Out[287]:
 Nasdaq    S&P 500  Russell 2000
 TradingDate
 2014/02/21   4282.17  1841.07  1166.25
 2014/02/24   4273.32  1836.78  1166.74
 2014/02/25   4298.48  1847.66  1176.00
 2014/02/26   4300.45  1845.79  1176.11
 2014/02/27   4291.47  1844.90  1179.28
 2014/02/28   4323.52  1855.12  1189.19
 6 rows × 3 columns

对于上述命令,更复杂的选择是使用swaplevelTradingDatePriceType级别之间切换,然后执行以下选择:

In [305]: sharesIndexDF.swaplevel(0, 1, axis=0).ix['open']
Out[305]:     Nasdaq   S&P 500  Russell 2000
 TradingDate
 2014/02/21  4282.17  1841.07  1166.25
 2014/02/24  4273.32  1836.78  1166.74
 2014/02/25  4298.48  1847.66  1176.00
 2014/02/26  4300.45  1845.79  1176.11
 2014/02/27  4291.47  1844.90  1179.28
 2014/02/28  4323.52  1855.12  1189.19
 6 rows × 3 columns

使用.xs具有与上一节有关面向整数的索引的横截面相同的效果。

布尔索引

我们使用布尔索引来过滤或选择部分数据。 运算符如下:

运算符 符号
|
&
~

这些运算符一起使用时,必须使用括号进行分组。 使用上一部分中较早的数据帧,在这里,我们显示纳斯达克收盘价高于 4300 的交易日期:

In [311]: sharesIndexDataDF.ix[(sharesIndexDataDF['PriceType']=='close') & \
 (sharesIndexDataDF['Nasdaq']>4300) ]
Out[311]:        PriceType  Nasdaq   S&P 500   Russell 2000
 TradingDate
 2014/02/27   close  4318.93   1854.29   1187.94
 2014/02/28   close  4308.12   1859.45   1183.03
 2 rows × 4 columns

您还可以创建布尔条件,在其中可以使用数组过滤掉部分数据:

In [316]: highSelection=sharesIndexDataDF['PriceType']=='high'
 NasdaqHigh=sharesIndexDataDF['Nasdaq']<4300
 sharesIndexDataDF.ix[highSelection & NasdaqHigh]
Out[316]: TradingDate  PriceType Nasdaq  S&P 500  Russell 2000
 2014/02/21    high     4284.85  1846.13  1168.43

因此,前面的代码段显示了整个交易时段中纳斯达克综合指数保持在 4300 水平以下的数据集中的唯一日期。

isin和所有方法

与前几节中使用的标准运算符相比,这些方法使用户可以通过布尔索引实现更多功能。 isin方法获取值列表,并在序列或数据帧中与列表中的值匹配的位置返回带有True的布尔数组。 这使用户可以检查序列中是否存在一个或多个元素。 这是使用序列的插图:

In [317]:stockSeries=pd.Series(['NFLX','AMZN','GOOG','FB','TWTR'])
 stockSeries.isin(['AMZN','FB'])
Out[317]:0    False
 1     True
 2    False
 3     True
 4    False
 dtype: bool

在这里,我们使用布尔数组选择一个包含我们感兴趣的值的子序列:

In [318]: stockSeries[stockSeries.isin(['AMZN','FB'])]
Out[318]: 1    AMZN
 3      FB
 dtype: object

对于我们的数据帧示例,我们切换到一个更有趣的数据集,该数据集是针对那些对人类生物学有偏爱,对澳大利亚哺乳动物进行分类(属于我的宠物)的数据集:

In [324]: australianMammals=
 {'kangaroo': {'Subclass':'marsupial', 
 'Species Origin':'native'},
 'flying fox' : {'Subclass':'placental', 
 'Species Origin':'native'},
 'black rat': {'Subclass':'placental', 
 'Species Origin':'invasive'},
 'platypus' : {'Subclass':'monotreme', 
 'Species Origin':'native'},
 'wallaby' :  {'Subclass':'marsupial', 
 'Species Origin':'native'},
 'palm squirrel' : {'Subclass':'placental', 
 'Origin':'invasive'},
 'anteater':     {'Subclass':'monotreme', 'Origin':'native'},
 'koala':        {'Subclass':'marsupial', 'Origin':'native'}
}

有关哺乳动物的更多信息:有袋动物是袋装哺乳动物,单峰类是产卵的,胎盘可生幼年。 该信息的来源是这里

The is in and any all methods

上一个图像的来源是 Bennett 的小袋鼠

In [328]: ozzieMammalsDF=pd.DataFrame(australianMammals)
In [346]: aussieMammalsDF=ozzieMammalsDF.T; aussieMammalsDF
Out[346]:       Subclass  Origin
 anteater      monotreme	 native
 black rat     placental   invasive
 flying fox    placental   native
 kangaroo      marsupial   native
 koala          marsupial   native
 palm squirrel placental   invasive
 platypus      monotreme	 native
 wallaby   marsupial   native
 8 rows × 2 columns

让我们尝试选择澳大利亚本土的哺乳动物:

In [348]: aussieMammalsDF.isin({'Subclass':['marsupial'],'Origin':['native']})
Out[348]:    Subclass Origin
 anteater   False   True
 black rat   False   False
 flying fox   False   True
 kangaroo   True   True
 koala      True   True
 palm squirrel False False
 platypus   False   True
 wallaby   True   True
 8 rows × 2 columns

传递给isin的一组值可以是数组或字典。 这种方法有些奏效,但是我们可以通过结合isinall()方法创建遮罩来获得更好的结果:

In [349]: nativeMarsupials={'Mammal Subclass':['marsupial'],
 'Species Origin':['native']}
 nativeMarsupialMask=aussieMammalsDF.isin(nativeMarsupials).all(True)
 aussieMammalsDF[nativeMarsupialMask]
Out[349]:      Subclass   Origin
 kangaroo  marsupial  native
 koala      marsupial  native
 wallaby   marsupial  native
 3 rows × 2 columns

因此,我们看到袋鼠,考拉和小袋鼠是我们数据集中的原生有袋动物。 any()方法返回布尔数据帧中是否有任何元素为Trueall()方法过滤器返回布尔数据帧中是否所有元素都是True

其来源是这里

使用where()方法

where()方法用于确保布尔过滤的结果与原始数据具有相同的形状。 首先,我们将随机数生成器种子设置为 100,以便用户可以生成如下所示的相同值:

In [379]: np.random.seed(100)
 normvals=pd.Series([np.random.normal() for i in np.arange(10)])
 normvals
Out[379]: 0   -1.749765
 1    0.342680
 2    1.153036
 3   -0.252436
 4    0.981321
 5    0.514219
 6    0.221180
 7   -1.070043
 8   -0.189496
 9    0.255001
 dtype: float64

In [381]: normvals[normvals>0]
Out[381]: 1    0.342680
 2    1.153036
 4    0.981321
 5    0.514219
 6    0.221180
 9    0.255001
 dtype: float64

In [382]: normvals.where(normvals>0)
Out[382]: 0         NaN
 1    0.342680
 2    1.153036
 3         NaN
 4    0.981321
 5    0.514219
 6    0.221180
 7         NaN
 8         NaN
 9    0.255001
 dtype: float64

此方法似乎仅在序列情况下有用,因为在数据帧情况下我们免费获得此行为:

In [393]: np.random.seed(100) 
 normDF=pd.DataFrame([[round(np.random.normal(),3) for i in np.arange(5)] for j in range(3)], 
 columns=['0','30','60','90','120'])
 normDF
Out[393]:  0  30  60  90  120
 0  -1.750   0.343   1.153  -0.252   0.981
 1   0.514   0.221  -1.070  -0.189   0.255
 2  -0.458   0.435  -0.584   0.817   0.673
 3 rows × 5 columns
In [394]: normDF[normDF>0]
Out[394]:  0  30  60  90  120
 0   NaN   0.343   1.153   NaN   0.981
 1   0.514   0.221   NaN	  NaN   0.255
 2   NaN   0.435   NaN   0.817   0.673
 3 rows × 5 columns
In [395]: normDF.where(normDF>0)
Out[395]:  0  30  60  90  120
 0   NaN     0.343   1.153   NaN   0.981
 1   0.514   0.221   NaN     NaN   0.255
 2   NaN     0.435   NaN     0.817 0.673
 3   rows × 5 columns

where方法的逆运算为mask

In [396]: normDF.mask(normDF>0)
Out[396]:  0  30  60  90  120
 0  -1.750  NaN   NaN    -0.252  NaN
 1   NaN    NaN  -1.070  -0.189  NaN
 2  -0.458  NaN  -0.584   NaN    NaN
 3  rows × 5 columns

索引操作

为了完成本章,我们将讨论索引的操作。 当我们希望重新对齐数据或以其他方式选择数据时,有时需要对索引进行操作。 有多种操作:

set_index-允许在现有数据帧上创建索引并返回索引的数据帧。
正如我们之前所见:

In [939]: stockIndexDataDF=pd.read_csv('./stock_index_data.csv')
In [940]: stockIndexDataDF
Out[940]:   TradingDate  Nasdaq   S&P 500  Russell 2000
 0         2014/01/30   4123.13  1794.19  1139.36
 1         2014/01/31   4103.88  1782.59  1130.88
 2         2014/02/03   3996.96  1741.89  1094.58
 3         2014/02/04   4031.52  1755.20  1102.84
 4         2014/02/05   4011.55  1751.64  1093.59
 5         2014/02/06   4057.12  1773.43  1103.93

现在,我们可以如下设置索引:

In [941]: stockIndexDF=stockIndexDataDF.set_index('TradingDate')
In [942]: stockIndexDF
Out[942]:    Nasdaq   S&P 500  Russell 2000
 TradingDate
 2014/01/30  4123.13   1794.19  1139.36
 2014/01/31	4103.88   1782.59  1130.88
 2014/02/03  3996.96   1741.89  1094.58
 2014/02/04  4031.52   1755.20  1102.84
 2014/02/05  4011.55   1751.64  1093.59
 2014/02/06  4057.12   1773.43  1103.93

reset_index反转set_index

In [409]: stockIndexDF.reset_index()
Out[409]: 
 TradingDate   Nasdaq   S&P 500  Russell 2000
0   2014/01/30   4123.13   1794.19   1139.36
1   2014/01/31   4103.88   1782.59   1130.88
2   2014/02/03   3996.96   1741.89   1094.58
3   2014/02/04   4031.52   1755.20   1102.84
4   2014/02/05   4011.55   1751.64   1093.59
5   2014/02/06   4057.12   1773.43   1103.93
6 rows × 4 columns

总结

总而言之,有多种方法可以从 Pandas 中选择数据:

  • 我们可以使用基本索引,这与我们对访问数组中数据的了解最接近。
  • 我们可以将基于标签或整数的索引与关联的运算符一起使用。
  • 我们可以使用多重索引,它是包含多个字段的复合键的 Pandas 版本。
  • 我们可以使用布尔/逻辑索引。

有关在 Pandas 中建立索引的更多参考,请查看官方文档

在下一章中,我们将研究使用 Pandas 对数据进行分组,重塑和合并的主题。

五、Pandas 的操作,第二部分 -- 数据的分组,合并和重塑

在本章中,我们解决了在数据结构中重新排列数据的问题。 我们研究了各种函数,这些函数使我们能够通过在实际数据集上利用它们来重新排列数据。 这样的函数包括groupbyconcataggregateappend等。 我们将讨论的主题如下:

  • 数据聚合/分组
  • 合并和连接数据
  • 重塑数据

数据分组

我们经常详细介绍希望基于分组变量进行聚合或合并的粒度数据。 在以下各节中,我们将说明实现此目的的一些方法。

分组操作

groupby操作可以被认为是包含以下三个步骤的过程的一部分:

  • 分割数据集
  • 分析数据
  • 聚合或合并数据

groupby子句是对数据帧的操作。 序列是一维对象,因此对其执行groupby操作不是很有用。 但是,它可用于获取序列的不同行。 groupby操作的结果不是数据帧,而是数据帧对象的dict。 让我们从涉及世界上最受欢迎的运动-足球的数据集开始。

该数据集来自维基百科,其中包含自 1955 年成立以来欧洲俱乐部冠军杯决赛的数据。有关参考,您可以访问这里

使用以下命令将.csv文件转换为数据帧:

In [27]: uefaDF=pd.read_csv('./euro_winners.csv')
In [28]: uefaDF.head()
Out[28]:

The groupby operation

因此,输出显示了赛季,获胜和亚军俱乐部所属的国家,得分,场地和出勤人数。 假设我们要按获得的欧洲俱乐部冠军的数量来对各国进行排名。 我们可以使用groupby来做到这一点。 首先,我们将groupby应用于数据帧并查看结果的类型是什么:

In [84]: nationsGrp =uefaDF.groupby('Nation');
 type(nationsGrp)
Out[84]: pandas.core.groupby.DataFrameGroupBy

因此,我们看到nationsGrppandas.core.groupby.DataFrameGroupBy类型。 我们在其中使用groupby的列称为键。 我们可以通过在生成的DataFrameGroupBy对象上使用groups属性来查看组的外观:

In [97]: nationsGrp.groups
Out[97]: {'England': [12, 21, 22, 23, 24, 25, 26, 28, 43, 49, 52,
 56],
 'France': [37],
 'Germany': [18, 19, 20, 27, 41, 45, 57],
 'Italy': [7, 8, 9, 13, 29, 33, 34, 38, 40, 47, 51, 54],
 'Netherlands': [14, 15, 16, 17, 32, 39],
 'Portugal': [5, 6, 31, 48],
 'Romania': [30],
 'Scotland': [11],
 'Spain': [0, 1, 2, 3, 4, 10, 36, 42, 44, 46, 50, 53, 55],
 'Yugoslavia': [35]}

这基本上是一个字典,仅显示唯一的组和与每个组相对应的轴标签(在本例中为行号)。 组的数量通过使用len()函数获得:

In [109]: len(nationsGrp.groups)
Out[109]: 10

现在,我们可以通过将size()函数应用于该组,然后应用sort()函数(按位置排序),以降序显示每个国家的获胜次数:

In [99]: nationWins=nationsGrp.size() 
In [100] nationWins.sort(ascending=False)
 nationWins
Out[100]: Nation
 Spain          13
 Italy          12
 England        12
 Germany         7
 Netherlands     6
 Portugal        4
 Yugoslavia      1
 Scotland        1
 Romania         1
 France          1
 dtype: int64

size()函数返回一个序列,该序列以组名称作为索引,每个组的大小。 size()函数也是聚合函数。 我们将在本章后面检查聚合函数。

为了进一步按国家和俱乐部划分胜利,我们在应用size()sort()之前应用多列groupby函数:

In [106]: winnersGrp =uefaDF.groupby(['Nation','Winners'])
 clubWins=winnersGrp.size()
 clubWins.sort(ascending=False)
 clubWins
Out[106]: Nation       Winners 
 Spain        Real Madrid          9
 Italy        Milan                7
 Germany      Bayern Munich        5
 England      Liverpool            5
 Spain        Barcelona            4
 Netherlands  Ajax                 4
 England      Manchester United    3
 Italy        Internazionale       3
 Juventus             2
 Portugal     Porto                2
 Benfica              2
 England      Nottingham Forest    2
 Chelsea              1
 France       Marseille            1
 Yugoslavia   Red Star Belgrade    1
 Germany      Borussia Dortmund    1
 Hamburg              1
 Netherlands  Feyenoord            1
 PSV Eindhoven        1
 Romania      Steaua Bucuresti     1
 Scotland     Celtic               1
 England      Aston Villa          1
 dtype: int64

多列groupby通过将键列指定为列表来指定多个列用作键。 因此,我们可以看到,这场比赛中最成功的俱乐部是西班牙的皇家马德里。 现在,我们检查了更丰富的数据集,这将使我们能够说明groupby的更多功能。 此数据集还与足球相关,并提供了 2012-2013 赛季欧洲四大联赛的统计数据:

  • 英超联赛或 EPL
  • 西班牙甲级联赛或西甲
  • 意大利甲级联赛
  • 德国超级联赛或德甲联赛

此信息的来源位于这里

现在让我们像往常一样将目标统计数据读入数据帧中。 在这种情况下,我们使用月份在数据帧上创建一个行索引:

In [68]: goalStatsDF=pd.read_csv('./goal_stats_euro_leagues_2012-13.csv')
 goalStatsDF=goalStatsDF.set_index('Month')

我们看一下数据集前端和后端的快照:

In [115]: goalStatsDF.head(3)
Out[115]:         Stat          EPL  La Liga Serie A  Bundesliga
 Month
 08/01/2012  MatchesPlayed  20     20      10       10
 09/01/2012  MatchesPlayed  38     39      50       44
 10/01/2012  MatchesPlayed  31     31      39       27

In [116]: goalStatsDF.tail(3)
Out[116]:         Stat         EPL  La Liga Serie A  Bundesliga
 Month
 04/01/2013  GoalsScored  105   127     102      104
 05/01/2013  GoalsScored   96   109     102      92
 06/01/2013  GoalsScored NaN   80     NaN      NaN

在此数据帧中有两种度量-MatchesPlayedGoalsScored-数据首先由Stat排序,然后由Month排序。 请注意,tail()输出的最后一行除La Liga以外的所有列均具有NaN值,但我们将在后面详细讨论。 我们可以使用groupby显示统计信息,但这将按年份分组。 这是如何完成的:

In [117]: goalStatsGroupedByYear = goalStatsDF.groupby(
lambda Month: Month.split('/')[2])

然后,我们可以遍历生成的groupby对象并显示组。 在以下命令中,我们看到按年份分组的两组统计信息。 请注意,使用 lambda 函数从月份的第一天开始获取年份组。 有关 lambda 函数的更多信息,请转到这里

In [118]: for name, group in goalStatsGroupedByYear:
 print name
 print group
 2012
 Stat  EPL  La Liga  Serie A  Bundesliga
 Month
 08/01/2012  MatchesPlayed   20       20       10          10
 09/01/2012  MatchesPlayed   38       39       50          44
 10/01/2012  MatchesPlayed   31       31       39          27
 11/01/2012  MatchesPlayed   50       41       42          46
 12/01/2012  MatchesPlayed   59       39       39          26
 08/01/2012    GoalsScored   57       60       21          23
 09/01/2012    GoalsScored  111      112      133         135
 10/01/2012    GoalsScored   95       88       97          77
 11/01/2012    GoalsScored  121      116      120         137
 12/01/2012    GoalsScored  183      109      125          72
 2013
 Stat  EPL  La Liga  Serie A  Bundesliga
 Month
 01/01/2013  MatchesPlayed   42       40       40          18
 02/01/2013  MatchesPlayed   30       40       40          36
 03/01/2013  MatchesPlayed   35       38       39          36
 04/01/2013  MatchesPlayed   42       42       41          36
 05/01/2013  MatchesPlayed   33       40       40          27
 06/02/2013  MatchesPlayed  NaN       10      NaN         NaN
 01/01/2013    GoalsScored  117      121      104          51
 02/01/2013    GoalsScored   87      110      100         101
 03/01/2013    GoalsScored   91      101       99         106
 04/01/2013    GoalsScored  105      127      102         104
 05/01/2013    GoalsScored   96      109      102          92
 06/01/2013    GoalsScored  NaN       80      NaN         NaN

如果我们希望按单个月份分组,则需要将groupby与级别参数一起应用,如下所示:

In [77]: goalStatsGroupedByMonth = goalStatsDF.groupby(level=0)

In [81]: for name, group in goalStatsGroupedByMonth:
 print name
 print group
 print "\n"

01/01/2013
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
01/01/2013  MatchesPlayed   42       40       40          18
01/01/2013    GoalsScored  117      121      104          51

02/01/2013
 Stat  EPL  La Liga  Serie A  Bundesliga
Month 
02/01/2013  MatchesPlayed   30       40       40          36
02/01/2013    GoalsScored   87      110      100         101

03/01/2013
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
03/01/2013  MatchesPlayed   35       38       39          36
03/01/2013    GoalsScored   91      101       99         106

04/01/2013
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
04/01/2013  MatchesPlayed   42       42       41          36
04/01/2013    GoalsScored  105      127      102         104

05/01/2013
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
05/01/2013  MatchesPlayed   33       40       40          27
05/01/2013    GoalsScored   96      109      102          92

06/01/2013
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
06/01/2013  GoalsScored  NaN       80      NaN         NaN

06/02/2013
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
06/02/2013  MatchesPlayed  NaN       10      NaN         NaN

08/01/2012
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
08/01/2012  MatchesPlayed   20       20       10          10
08/01/2012    GoalsScored   57       60       21          23

09/01/2012
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
09/01/2012  MatchesPlayed   38       39       50          44
09/01/2012    GoalsScored  111      112      133         135

10/01/2012
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
10/01/2012  MatchesPlayed   31       31       39          27
10/01/2012    GoalsScored   95       88       97          77

11/01/2012
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
11/01/2012  MatchesPlayed   50       41       42          46
11/01/2012    GoalsScored  121      116      120         137

12/01/2012
 Stat  EPL  La Liga  Serie A  Bundesliga
Month
12/01/2012  MatchesPlayed   59       39       39          26
12/01/2012    GoalsScored  183      109      125          72

注意,由于在前面的命令中我们将索引分组,因此需要指定级别参数,而不是仅使用列名。 当我们按多个键分组时,得到的分组名称是一个元组,如后面的命令所示。 首先,我们重置索引以获得原始数据帧并定义一个多重索引以便能够按多个键进行分组。 如果不这样做,将导致ValueError

In [246]: goalStatsDF=goalStatsDF.reset_index()
 goalStatsDF=goalStatsDF.set_index(['Month','Stat'])

In [247]: monthStatGroup=goalStatsDF.groupby(level=['Month','Stat'])

In [248]: for name, group in monthStatGroup:
 print name
 print group

('01/01/2013', 'GoalsScored')
 EPL  La Liga  Serie A  Bundesliga
Month      Stat
01/01/2013 GoalsScored    117      121   104      51
('01/01/2013', 'MatchesPlayed')
 EPL  La Liga  Serie A  Bundesliga
Month      Stat
01/01/2013 MatchesPlayed   42       40    40       18
('02/01/2013', 'GoalsScored')
 EPL  La Liga  Serie A  Bundesliga
Month      Stat
02/01/2013 GoalsScored   87      110   100      101

将分组与多重索引一起使用

如果我们的数据帧具有多重索引,则可以使用groupby按层次结构的不同级别分组并计算一些有趣的统计数据。 这是使用由MonthStat组成的多重索引的目标统计数据:

In [134]:goalStatsDF2=pd.read_csv('./goal_stats_euro_leagues_2012-13.csv')
 goalStatsDF2=goalStatsDF2.set_index(['Month','Stat'])
In [141]: print goalStatsDF2.head(3)
 print goalStatsDF2.tail(3)
 EPL  La Liga  Serie A  Bundesliga
Month      Stat
08/01/2012 MatchesPlayed   20       20       10          10
09/01/2012 MatchesPlayed   38       39       50          44
10/01/2012 MatchesPlayed   31       31       39          27
 EPL  La Liga  Serie A  Bundesliga
Month      Stat
04/01/2013 GoalsScored  105      127      102         104
05/01/2013 GoalsScored   96      109      102          92
06/01/2013 GoalsScored  NaN       80      NaN         NaN

假设我们希望计算每个联赛的进球总数和整个赛季的总比赛数,我们可以这样做:

In [137]: grouped2=goalStatsDF2.groupby(level='Stat')
In [139]: grouped2.sum()
Out[139]:         EPL   La Liga  Serie A  Bundesliga   Stat
 GoalsScored   1063  1133     1003  898
 MatchesPlayed 380    380      380  306

顺便说一句,通过直接使用sum并将级别作为参数传递,可以获得与前一个结果相同的结果:

In [142]: goalStatsDF2.sum(level='Stat')
Out[142]:            EPL   La Liga  Serie A  Bundesliga   Stat
 GoalsScored    1063  1133     1003  898
 MatchesPlayed   380  380      380  306

现在,让我们获取一个关键统计数据,以确定每个联赛中本赛季的兴奋程度 - 每场比赛的进球数比:

In [174]: totalsDF=grouped2.sum()

In [175]: totalsDF.ix['GoalsScored']/totalsDF.ix['MatchesPlayed']
Out[175]: EPL           2.797368
 La Liga       2.981579
 Serie A       2.639474
 Bundesliga    2.934641
 dtype: float64

如上一条命令所示,它作为序列返回。 现在,我们可以显示每场比赛的进球数,进球数和比赛数,以概述联盟的兴奋程度,如下所示:

  1. 获得每个游戏数据的目标作为数据帧。 请注意,由于gpg作为序列返回,因此我们必须对其进行转置:

    In [234]: gpg=totalsDF.ix['GoalsScored']/totalsDF.ix['MatchesPlayed']
     goalsPerGameDF=pd.DataFrame(gpg).T
    
    In [235]: goalsPerGameDF
    Out[235]:     EPL   La Liga   Serie A   Bundesliga
     0   2.797368   2.981579  2.639474  2.934641
    
    
  2. 重新索引goalsPerGameDF数据帧,以便将0索引替换为GoalsPerGame

    In [207]: goalsPerGameDF=goalsPerGameDF.rename(index={0:'GoalsPerGame'}) 
    
    In [208]: goalsPerGameDF
    Out[208]:          EPL      La Liga   Serie A   Bundesliga
     GoalsPerGame  2.797368  2.981579  2.639474  2.934641
    
    
  3. goalsPerGameDF数据帧追加到原始数据帧:

    In [211]: pd.options.display.float_format='{:.2f}'.format
     totalsDF.append(goalsPerGameDF)
    Out[211]:      EPL    La Liga     Serie A   Bundesliga
     GoalsScored    1063.00  1133.00  1003.00   898.00
     MatchesPlayed  380.00    380.00   380.00   306.00
     GoalsPerGame      2.80    2.98     2.64   2.93
    
    

下图显示了我们讨论过的 1955-2012 年欧洲联赛每场比赛的进球数。 可以在这个链接中找到其来源。

Using groupby with a MultiIndex

使用聚合方法

生成摘要统计信息的另一种方法是显式使用聚合方法:

In [254]: pd.options.display.float_format=None
In [256]: grouped2.aggregate(np.sum)
Out[256]:       EPL  La Liga  Serie A  Bundesliga   Stat
 GoalsScored     1063  1133  1003   898
 MatchesPlayed  380    380   380   306

这将生成一个分组的数据帧对象,该对象在前面的命令中显示。 我们还将浮点格式重置为None,因此由于上一节中的格式设置,整数值数据将不会显示为浮点。

应用多种函数

对于分组的数据帧对象,我们可以指定要应用于每列的函数列表:

In [274]: grouped2.agg([np.sum, np.mean,np.size])
Out[274]:      EPL          La Liga      Serie A        Bundesliga
 sum mean size  sum mean size  sum mean size sum mean size Stat
 GoalsScored  1063 106.3 11 1133 103.0 11 1003 100.3 11 898 89.8  11
 MatchesPlayed 380 38.0 11  380 34.6  11  380 38.0 11  306 30.6  11

请注意,上述显示 NA 值的输出已从聚合计算中排除。 agg是聚合的缩写形式。 因此,英超联赛,意甲联赛和德甲联赛的均值的计算是基于 10 个月而不是 11 个月的。这是因为在 6 月的最后一个月,这三个联赛中没有进行过比赛,这与西甲相反, 六月有比赛。

对于成组的序列赛,我们返回到nationsGrp示例,并计算锦标赛获胜者所在国家/地区的出勤率统计数据:

In [297]: nationsGrp['Attendance'].agg({'Total':np.sum, 'Average':np.mean, 'Deviation':np.std})
Out[297]:       Deviation   Average     Total
 Nation
 England    17091.31    66534.25   798411
 France     NaN         64400      64400
 Germany    13783.83    67583.29   473083
 Italy       17443.52    65761.25   789135
 Netherlands 16048.58   67489.0    404934
 Portugal    15632.86   49635.5    198542
 Romania     NaN      70000       70000
 Scotland    NaN      45000        45000
 Spain        27457.53   73477.15   955203
 Yugoslavia  NaN      56000      56000

对于分组的序列,我们可以传递函数列表或dict。 在前面的情况下,指定了dict,并且将键值用作结果数据帧中列的名称。 请注意,在单个样本大小的组的情况下,标准差未定义,结果为NaN,例如,罗马尼亚。

transform()方法

groupby-transform函数用于对groupby对象执行转换操作。 例如,我们可以使用fillna方法替换groupby对象中的NaN值。 使用转换后得到的对象具有与原始groupby对象相同的大小。 让我们考虑一个数据帧架,该数据帧架显示四个足球联赛中每个月的得分目标:

In[344]: goalStatsDF3=pd.read_csv('./goal_stats_euro_leagues_2012-13.csv')
goalStatsDF3=goalStatsDF3.set_index(['Month'])
goalsScoredDF=goalStatsDF3.ix[goalStatsDF3['Stat']=='GoalsScored']

goalsScoredDF.iloc[:,1:]
Out[344]:        EPL  La Liga  Serie A  Bundesliga
Month
08/01/2012   57   60    21         23
09/01/2012   111   112    133        135
10/01/2012   95   88    97         77
11/01/2012   121   116    120        137
12/01/2012   183   109    125         72
01/01/2013   117   121    104         51
02/01/2013   87   110    100        101
03/01/2013   91   101    99        106
04/01/2013   105   127    102        104
05/01/2013   96   109    102         92
06/01/2013   NaN   80    NaN        NaN

我们可以看到,在 2013 年 6 月,参加比赛的唯一联赛是La Liga,得出了其他三个联赛的NaN值。 让我们按年份对数据进行分组:

In [336]: goalsScoredPerYearGrp=goalsScoredDF.groupby(lambda Month: Month.split('/')[2])
 goalsScoredPerYearGrp.mean()
Out[336]:           EPL    La Liga   Serie A  Bundesliga
 2012       113.4   97        99.2     88.8
 2013       99.2    108       101.4    90.8

前面的函数利用 lambda 函数通过分割/字符上的Month变量并采用结果列表的第三个元素来获取年份。

如果我们计算各个联赛中每年举行比赛的月份数,那么我们有:

In [331]: goalsScoredPerYearGrp.count()
Out[331]:         EPL  La Liga  Serie A  Bundesliga
 2012     5     5       5         5
 2013     5     6       5         5

通常不希望显示具有缺失值的数据,而解决这种情况的一种常用方法是将缺失值替换为组均值。 这可以使用transform, groupby函数来实现。 首先,我们必须使用 lambda 函数定义转换,然后使用transform方法应用此转换:

In [338]: fill_fcn = lambda x: x.fillna(x.mean())
 trans = goalsScoredPerYearGrp.transform(fill_fcn)
 tGroupedStats = trans.groupby(lambda Month:   Month.split('/')[2])
 tGroupedStats.mean() 
Out[338]:           EPL     La Liga   Serie A  Bundesliga
 2012       113.4   97        99.2     88.8
 2013       99.2    108       101.4    90.8

从前面的结果中要注意的一件事是,将NaN值替换为原始组中的组均值,会使该组均值在转换后的数据中保持不变。

但是,当我们对转换后的组进行计数时,我们发现 EPL,意甲和德甲的比赛数从 5 变为 6:

In [339]: tGroupedStats.count()
Out[339]:        EPL    La Liga   Serie A  Bundesliga
 2012     5     5         5          5
 2013     6     6         6          6

过滤

filter方法使我们能够对groupby对象应用过滤,该过滤会产生初始对象的子集。 在这里,我们说明了如何显示本赛季的月份,四个联赛中每个赛季都进球超过 100 个进球:

In [391]:  goalsScoredDF.groupby(level='Month').filter(lambda x: 
 np.all([x[col] > 100 
 for col in goalsScoredDF.columns]))
Out[391]:            EPL  La Liga  Serie A  Bundesliga
 Month
 09/01/2012   111   112       133     135
 11/01/2012   121   116       120     137
 04/01/2013   105   127       102     104

请注意,使用np.all运算符可确保对所有列强制实现约束。

合并和连接

有多种函数可用于合并和连接 Pandas 的数据结构,其中包括以下函数:

  • concat
  • append

concat函数

concat函数用于沿指定的轴连接多个 Pandas 的数据结构,并可能沿其他轴执行合并或相交操作。 以下命令说明concat函数:

concat(objs, axis=0, , join='outer', join_axes=None, ignore_index=False,
 keys=None, levels=None, names=None, verify_integrity=False)

concat函数的元素概述如下:

  • objs函数:要连接的序列,数据帧或面板对象的列表或字典。
  • axis函数:应当执行级联的轴。 默认值为0
  • join函数:处理其他轴上的索引时要执行的连接类型。 默认为'outer'函数。
  • join_axes函数:该函数用于为其余索引指定确切的索引,而不是进行外部/内部连接。
  • keys函数:这指定了用于构造多重索引的键的列表。

有关其余选项的说明,请参阅文档

这是使用前面章节中的股价示例来说明concat的工作原理:

In [53]: stockDataDF=pd.read_csv('./tech_stockprices.csv').set_index(['Symbol']);stockDataDF
Out[53]:
 Closing price  EPS  Shares Outstanding(M) P/E Market Cap(B) Beta
Symbol
 AAPL   501.53    40.32  892.45         12.44   447.59    0.84
 AMZN   346.15    0.59   459.00         589.80  158.88    0.52
 FB     61.48     0.59   2450.00        104.93  150.92    NaN
 GOOG   1133.43   36.05  335.83         31.44   380.64    0.87
 TWTR   65.25    -0.30   555.20         NaN     36.23     NaN
 YHOO   34.90     1.27   1010.00        27.48   35.36     0.66

现在,我们获取各种数据片段:

In [83]: A=stockDataDF.ix[:4, ['Closing price', 'EPS']]; A
Out[83]:  Closing price  EPS
 Symbol
 AAPL     501.53      40.32
 AMZN     346.15     0.59
 FB      61.48     0.59
 GOOG    1133.43    36.05

In [84]: B=stockDataDF.ix[2:-2, ['P/E']];B
Out[84]:         P/E
 Symbol
 FB     104.93
 GOOG   31.44

In [85]: C=stockDataDF.ix[1:5, ['Market Cap(B)']];C
Out[85]:         Market Cap(B)
 Symbol
 AMZN   158.88
 FB     150.92
 GOOG   380.64
 TWTR   36.23

在这里,我们通过指定外部连接来执行连接,该外部连接对所有三个数据帧进行连接并执行并集,并通过为此类列插入NaN来包括所有列均不具有值的条目:

In [86]: pd.concat([A,B,C],axis=1) # outer join
Out[86]:  Closing price  EPS    P/E   Market Cap(B)
 AAPL   501.53     40.32  NaN   NaN
 AMZN   346.15     0.59   NaN   158.88
 FB     61.48      0.59   104.93 150.92
 GOOG   1133.43    36.05  31.44 380.64
 TWTR   NaN        NaN    NaN    36.23 

我们还可以指定一个内部连接来进行连接,但是通过丢弃缺少列的行来只包含包含最终数据帧中所有列值的行,也就是说,它需要交集:

In [87]: pd.concat([A,B,C],axis=1, join='inner') # Inner join
Out[87]:        Closing price  EPS  P/E   Market Cap(B)
 Symbol
 FB      61.48    0.59 104.93  150.92
 GOOG    1133.43   36.05   31.44   380.64

第三种情况使我们能够使用原始数据帧中的特定索引进行连接:

In [102]: pd.concat([A,B,C], axis=1, join_axes=[stockDataDF.index])
Out[102]:       Closing price  EPS    P/E   Market Cap(B)
 Symbol
 AAPL   501.53     40.32  NaN   NaN
 AMZN   346.15     0.59   NaN   158.88
 FB     61.48      0.59  104.93 150.92
 GOOG   1133.43    36.05  31.44 380.64
 TWTR   NaN        NaN    NaN    36.23
 YHOO   NaN        NaN    NaN    NaN

在这最后一种情况下,我们看到YHOO的行已包括在内,即使它不包含在任何连接的切片中。 但是,在这种情况下,所有列的值为NaN。 这是concat的另一种说明,但是这次是随机统计分布。 请注意,在没有轴参数的情况下,默认的连接轴为0

In[135]: np.random.seed(100)
 normDF=pd.DataFrame(np.random.randn(3,4));normDF
Out[135]:    0    1      2    3
 0  -1.749765  0.342680  1.153036  -0.252436
 1   0.981321  0.514219  0.221180  -1.070043
 2  -0.189496  0.255001 -0.458027   0.435163

In [136]: binomDF=pd.DataFrame(np.random.binomial(100,0.5,(3,4)));binomDF
Out[136]:    0  1  2  3
 0  57  50  57     50
 1  48  56  49     43
 2  40  47  49     55

In [137]: poissonDF=pd.DataFrame(np.random.poisson(100,(3,4)));poissonDF
Out[137]:  0  1  2  3
 0  93  96  96  89
 1  76  96  104  103
 2  96  93  107   84

In [138]: rand_distribs=[normDF,binomDF,poissonDF]
In [140]: rand_distribsDF=pd.concat(rand_distribs,keys=['Normal', 'Binomial', 'Poisson']);rand_distribsDF
Out[140]:         0        1       2          3
 Normal     0  -1.749765   0.342680  1.153036  -0.252436
 1   0.981321   0.514219  0.221180  -1.070043
 2  -0.189496   0.255001 -0.458027   0.435163
 Binomial 0   57.00       50.00     57.00      50.00
 1   48.00       56.00     49.00      43.00
 2   40.00       47.00     49.00      55.00
 Poisson  0   93.00       96.00     96.00      89.00
 1   76.00       96.00    104.00     103.00
 2   96.00       93.00    107.00      84.00

附加

append函数是concat的简单版本,沿着axis=0连接在一起。 这是其用法的说明,其中我们将stockData数据帧的前两行和前三列切成薄片:

In [145]: stockDataA=stockDataDF.ix[:2,:3]
 stockDataA
Out[145]:  Closing price  EPS   Shares Outstanding(M)
 Symbol
 AAPL     501.53   40.32   892.45
 AMZN     346.15   0.59   459.00

其余的行:

In [147]: stockDataB=stockDataDF[2:]
 stockDataB
Out[147]:
 Closing price EPS Shares Outstanding(M)  P/E  Market Cap(B) Beta
Symbol
FB   61.48         0.59  2450.00          104.93 150.92   NaN
GOOG   1133.43    36.05   335.83          31.44  380.64   0.87
TWTR     65.25    -0.30   555.20           NaN     36.23   NaN
YHOO     34.90  1.27  1010.00       27.48  35.36   0.66

现在,我们使用append合并来自前面命令的两个数据帧:

In [161]:stockDataA.append(stockDataB)
Out[161]:
 Beta Closing price EPS MarketCap(B) P/E    Shares Outstanding(M)
 Symbol
 AMZN  NaN    346.15    0.59  NaN   NaN    459.00
 GOOG  NaN    1133.43   36.05  NaN   NaN    335.83
 FB    NaN    61.48     0.59  150.92 104.93 2450.00
 YHOO  27.48  34.90     1.27  35.36   0.66   1010.00
 TWTR  NaN    65.25    -0.30  36.23   NaN    555.20
 AAPL  12.44  501.53    40.32  0.84   447.59 892.45

为了保持类似于原始数据帧的列顺序,我们可以应用reindex_axis函数:

In [151]: stockDataA.append(stockDataB).reindex_axis(stockDataDF.columns, axis=1)
Out[151]:
 Closing price EPS Shares Outstanding(M)  P/E Market Cap(B) Beta
 Symbol
 AAPL   501.53  40.32  892.45         NaN  NaN      NaN
 AMZN   346.15   0.59  459.00         NaN  NaN      NaN
 FB     61.48     0.59  2450.00       104.93  150.92      NaN
 GOOG   1133.43  36.05  335.83        31.44  380.64     0.87
 TWTR   65.25  -0.30  555.20         NaN   36.23      NaN
 YHOO   34.90     1.27  1010.00       27.48  35.36     0.66

请注意,对于前两行,后两列的值为NaN,因为第一个数据帧仅包含前三列。 append函数无法在某些地方工作,但是会返回一个新的数据帧,并将第二个数据帧附加到第一个数据帧上。

将一行附加到数据帧

我们可以通过将序列或字典传递给append方法来将单个行附加到数据帧:

In [152]: 
algos={'search':['DFS','BFS','Binary Search','Linear'],
 'sorting': ['Quicksort','Mergesort','Heapsort','Bubble Sort'],
 'machine learning':['RandomForest','K Nearest Neighbor','Logistic Regression','K-Means Clustering']}
algoDF=pd.DataFrame(algos);algoDF
Out[152]: machine learning    search      sorting
 0    RandomForest        DFS      Quicksort
 1    K Nearest Neighbor   BFS      Mergesort
 2    Logistic Regression  Binary Search Heapsort
 3    K-Means Clustering   Linear       Bubble Sort

In [154]: 
moreAlgos={'search': 'ShortestPath'  , 'sorting': 'Insertion Sort',
 'machine learning': 'Linear Regression'}
 algoDF.append(moreAlgos,ignore_index=True)
Out[154]: machine learning    search      sorting
 0    RandomForest        DFS      Quicksort
 1    K Nearest Neighbor    BFS      Mergesort
 2    Logistic Regression Binary Search Heapsort
 3    K-Means Clustering  Linear       Bubble Sort
 4    Linear Regression   ShortestPath  Insertion Sort

为了使它起作用,必须传递ignore_index=True参数,以便忽略algoDF中的index [0,1,2,3]

类似于 SQL 的数据帧对象的合并/连接

merge函数用于获取两个数据帧对象的连接,类似于 SQL 数据库查询中使用的那些连接。数据帧对象类似于 SQL 表。 以下命令对此进行了说明:

merge(left, right, how='inner', on=None, left_on=None,
 right_on=None, left_index=False, right_index=False, 
 sort=True, suffixes=('_x', '_y'), copy=True)

以下是merge函数的摘要:

  • left参数:这是第一个数据帧对象
  • right参数:这是第二个数据帧对象
  • how参数:这是连接的类型,可以是内部,外部,左侧或右侧。 默认值为内部。
  • on参数:这显示要作为连接键进行连接的列的名称。
  • left_onright_on参数:这显示了要连接的左右DataFrame列名称。
  • left_indexright_index参数:这具有布尔值。 如果这是True,请使用左或右DataFrame索引/行标签进行连接。
  • sort参数:这是一个布尔值。 默认的True设置将按字典顺序进行排序。 将默认值设置为False可能会提高性能。
  • suffixes参数:应用于重叠列的字符串后缀的元组。 默认值为'_x''_y'
  • copy参数:默认True值导致从传递的DataFrame对象中复制数据。

可以在这个链接中找到上述信息的来源。

让我们开始通过将美国股票指数数据读取到DataFrame中来研究合并的使用:

In [254]: USIndexDataDF=pd.read_csv('./us_index_data.csv')
 USIndexDataDF
Out[254]:    TradingDate  Nasdaq   S&P 500  Russell 2000  DJIA
 0   2014/01/30   4123.13  1794.19       1139.36  15848.61
 1   2014/01/31   4103.88  1782.59   1130.88  15698.85
 2   2014/02/03   3996.96  1741.89   1094.58  15372.80
 3   2014/02/04   4031.52  1755.20   1102.84  15445.24
 4   2014/02/05   4011.55  1751.64   1093.59  15440.23
 5   2014/02/06   4057.12  1773.43   1103.93  15628.53

可在这个链接中找到此信息的来源。

我们可以使用以下命令获取第 0 行和第 1 行以及NasdaqS&P 500列的数据slice1

In [255]: slice1=USIndexDataDF.ix[:1,:3]
 slice1
Out[255]:   TradingDate  Nasdaq         S&P 500
 0       2014/01/30  4123.13   1794.19
 1       2014/01/31  4103.88   1782.59

我们可以使用以下命令获取第 0 行和第 1 行以及Russell 2000DJIA列的数据slice2

In [256]: slice2=USIndexDataDF.ix[:1,[0,3,4]]
 slice2
Out[256]:   TradingDate  Russell 2000    DJIA
 0       2014/01/30  1139.36     15848.61
 1       2014/01/31  1130.88     15698.85

我们可以使用以下命令获取第 1 行和第 2 行以及NasdaqS&P 500列的数据slice3

In [248]: slice3=USIndexDataDF.ix[[1,2],:3]
 slice3
Out[248]:   TradingDate      Nasdaq    S&P 500
 1  2014/01/31       4103.88   1782.59
 2  2014/02/03       3996.96   1741.89

现在,我们可以如下合并slice1slice2

In [257]: pd.merge(slice1,slice2)
Out[257]:   TradingDate  Nasdaq	S&P 500  Russell 2000  DJIA
 0  2014/01/30   4123.13  1794.19   1139.36     15848.61
 1  2014/01/31     4103.88  1782.59   1130.88     15698.85

如您所见,这将导致slice1slice2中的列的组合。 由于未指定on自变量,因此使用slice1slice2中的列相交,即TradingDate作为连接列,而slice1slice2中的其余列用于产生输出。

注意,在这种情况下,传递how的值对结果没有影响,因为slice1slice2TradingDate连接键值匹配。

现在,我们合并slice3slice2,将inner指定为how参数的值:

In [258]: pd.merge(slice3,slice2,how='inner')
Out[258]:   TradingDate  Nasdaq	     S&P 500  Russell 2000   DJIA
 0  2014/01/31   4103.88   1782.59    1130.88      15698.85

slice3参数的TradingDate值为2014/01/312014/02/03唯一值,slice2TradingDate值为2014/01/302014/01/31唯一值。

merge函数使用这些值的交集,即2014/01/31。 这将导致单行结果。 在这里,我们将outer指定为how参数的值:

In [269]: pd.merge(slice3,slice2,how='outer')
Out[269]:   TradingDate  Nasdaq     S&P 500  Russell 2000  DJIA
 0  2014/01/31  4103.88   1782.59   1130.88    15698.85
 1  2014/02/03  3996.96   1741.89   NaN          NaN
 2  2014/01/30      NaN   NaN   1139.36    15848.61

指定outer会使用两个数据帧中的所有键(联合),这将提供在先前输出中指定的三行。 由于并非所有列都存在于两个数据帧中,因此对于不属于交集的数据帧中的每一行,来自另一个数据帧的列均为NaN

现在,我们指定how='left',如以下命令所示:

In [271]: pd.merge(slice3,slice2,how='left')
Out[271]:  TradingDate  Nasdaq   S&P 500  Russell 2000   DJIA
 0  2014/01/31   4103.88  1782.59  1130.88         15698.85
 1  2014/02/03  3996.96   1741.89  NaN         NaN

在这里,我们看到左侧数据帧slice3的键用于输出。 对于slice3中不可用的列,即Russell 2000DJIA, NaN用于TradingDate2014/02/03的行。 这等效于 SQL 左外部连接。

我们在以下命令中指定how='right'

In [270]: pd.merge(slice3,slice2,how='right')
Out[270]:   TradingDate  Nasdaq   S&P 500  Russell 2000  DJIA
 0  2014/01/31  4103.88   1782.59  1130.88  15698.85
 1  2014/01/30  NaN      NaN  1139.36  15848.61

这是所使用的右侧数据帧slice2how='left'键的推论。 因此,结果为TradingDate2014/01/312014/01/30的行。 对于不在slice2-NasdaqS&P 500-NaN中的列。

这等效于 SQL 右外部连接。 有关 SQL 连接如何工作的简单说明,请参考这里

join函数

DataFrame.join函数用于合并两个具有不同列且没有共同点的数据帧。 本质上,这是两个数据帧的纵向连接。 这是一个例子:

In [274]: slice_NASD_SP=USIndexDataDF.ix[:3,:3]
 slice_NASD_SP
Out[274]:   TradingDate  Nasdaq  S&P 500
 0  2014/01/30    4123.13  1794.19
 1  2014/01/31    4103.88  1782.59
 2  2014/02/03    3996.96  1741.89
 3  2014/02/04    4031.52  1755.20

In [275]: slice_Russ_DJIA=USIndexDataDF.ix[:3,3:]
 slice_Russ_DJIA
Out[275]:   Russell 2000   DJIA
 0    1139.36       15848.61
 1    1130.88       15698.85
 2    1094.58       15372.80
 3    1102.84       15445.24

在这里,我们将join运算符称为:

In [276]: slice_NASD_SP.join(slice_Russ_DJIA)
Out[276]: TradingDate  Nasdaq  S&P 500  Russell 2000   DJIA
 0  2014/01/30  4123.13  1794.19   1139.36    15848.61
 1  2014/01/31  4103.88  1782.59   1130.88    15698.85
 2  2014/02/03  3996.96  1741.89   1094.58    15372.80
 3  2014/02/04  4031.52  1755.20   1102.84    15445.24

在这种情况下,我们看到结果是来自两个数据帧的列的组合。 让我们看看当尝试将join两个具有共同列的数据帧一起使用时会发生什么:

In [272]: slice1.join(slice2)
------------------------------------------------------------
Exception                  Traceback (most recent call last)
...

Exception: columns overlap: Index([u'TradingDate'], dtype=object)

由于列重叠而导致异常。 您可以在官方文档页面中找到有关使用mergeconcatjoin操作的更多信息。

数据透视和重塑

本节介绍如何重塑数据。 有时,数据以堆叠的格式存储。 这是使用PlantGrowth数据集的堆叠数据的示例:

In [344]: plantGrowthRawDF=pd.read_csv('./PlantGrowth.csv')
 plantGrowthRawDF
Out[344]:     observation   weight  group
 0    1             4.17    ctrl
 1    2             5.58    ctrl
 2    3             5.18    ctrl
 ...
 10   1             4.81    trt1
 11   2             4.17    trt1
 12   3             4.41    trt1
 ... 
 20   1             6.31    trt2
 21   2             5.12    trt2
 22   3             5.54    trt2

此数据包含比较在对照ctrl)和两种不同处理条件(trt1trt2)下获得的植物的干重产量的实验结果。 假设我们想按组值对该数据进行一些分析。 一种方法是在数据帧上使用逻辑过滤器:

In [346]: plantGrowthRawDF[plantGrowthRawDF['group']=='ctrl']
Out[346]:   observation   weight  group
 0     1      4.17   ctrl
 1   2      5.58   ctrl
 2   3      5.18   ctrl
 3   4      6.11   ctrl
 ...

这可能是乏味的,所以我们改为希望对数据进行透视/堆叠并以更有利于分析的形式显示。 我们可以使用DataFrame.pivot函数执行以下操作:

In [345]: plantGrowthRawDF.pivot(index='observation',columns='group',values='weight')
Out[345]: weight 
 group   ctrl   trt1   trt2
 observation
 1       4.17   4.81   6.31
 2       5.58   4.17   5.12
 3       5.18   4.41   5.54
 4       6.11   3.59   5.50
 5       4.50   5.87   5.37
 6       4.61   3.83   5.29
 7       5.17   6.03   4.92
 8       4.53   4.89   6.15
 9       5.33   4.32   5.80
 10      5.14   4.69   5.26

在这里,使用与组的不同值相对应的列创建数据帧架,或者用统计学的话来说,就是因子水平。 通过 Pandas pivot_table函数可以达到相同的结果,如下所示:

In [427]: pd.pivot_table(plantGrowthRawDF,values='weight', 
 rows='observation', cols=['group'])

Out[427]:   group  ctrl   trt1   trt2
 observation
 1       4.17   4.81   6.31
 2       5.58   4.17   5.12
 3       5.18   4.41   5.54
 4       6.11   3.59   5.50
 5       4.50   5.87   5.37
 6       4.61   3.83   5.29
 7       5.17   6.03   4.92
 8       4.53   4.89   6.15
 9       5.33   4.32   5.80

10      5.14   4.69   5.26

pivotpivot_table函数之间的主要区别在于pivot_table允许用户指定一个聚合函数,可以在该函数上聚合值。 因此,例如,如果我们希望获得 10 个观测值中每个组的平均值,我们将执行以下操作,这将得出一个序列:

In [430]: pd.pivot_table(plantGrowthRawDF,values='weight',cols=['group'],aggfunc=np.mean)
Out[430]: group
 ctrl     5.032
 trt1     4.661
 trt2     5.526
 Name: weight, dtype: float64

pivot_table的完整提要可从这里获得。 您可以在这里这里找到有关其用法的更多信息和示例。

堆叠

pivot函数外,stackunstack函数在序列和数据帧上也可用,它们可用于包含多重索引的对象。

stack()函数

首先,我们将组和观察列的值分别设置为行索引的组成部分,从而得到多重索引:

In [349]: plantGrowthRawDF.set_index(['group','observation'])
Out[349]:                    weight
 group  observation
 ctrl     1         4.17
 2         5.58
 3         5.18
 ...
 trt1     1         4.81
 2         4.17
 3         4.41
 ...
 trt2     1        6.31
 2        5.12
 3        5.54
 ...

在这里,我们看到行索引由组上的多重索引和以 weight 列作为数据值的观察组成。 现在,让我们看看如果将unstack应用于group级别会发生什么:

In [351]: plantGrowthStackedDF.unstack(level='group')
Out[351]:                weight
 group   ctrl trt1   trt2
 observation
 1     4.17 4.81   6.31
 2     5.58 4.17   5.12
 3     5.18 4.41   5.54
 4     6.11 3.59   5.50
 5     4.50 5.87   5.37
 6     4.61 3.83   5.29
 7     5.17 6.03   4.92
 8     4.53 4.89   6.15
 9    5.33   4.32  5.80
 10    5.14   4.69  5.26

以下调用等效于前面的调用:plantGrowthStackedDF.unstack(level=0)

在这里,我们可以看到数据帧已旋转,并且该组现在已从行索引(标题)更改为列索引(标题),从而使数据帧看起来更加紧凑。 为了更详细地了解正在发生的事情,我们首先将多重索引作为行索引放在组的观察上:

In [356]: plantGrowthStackedDF.index
Out[356]: MultiIndex
 [(u'ctrl', 1), (u'ctrl', 2), (u'ctrl', 3), (u'ctrl', 4), (u'ctrl', 5), 
 (u'ctrl', 6), (u'ctrl', 7), (u'ctrl', 8), (u'ctrl', 9), (u'ctrl', 10), 
 (u'trt1', 1), (u'trt1', 2), (u'trt1', 3), (u'trt1', 4), (u'trt1', 5), 
 (u'trt1', 6), (u'trt1', 7), (u'trt1', 8), (u'trt1', 9), (u'trt1', 10), 
 (u'trt2', 1), (u'trt2', 2), (u'trt2', 3), (u'trt2', 4), (u'trt2', 5), 
 (u'trt2', 6), (u'trt2', 7), (u'trt2', 8), (u'trt2', 9), (u'trt2', 10)]

In [355]: plantGrowthStackedDF.columns
Out[355]: Index([u'weight'], dtype=object)

取消堆叠操作从行索引中删除组,将其更改为单级索引:

In [357]: plantGrowthStackedDF.unstack(level='group').index
Out[357]: Int64Index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=int64)

现在,多重索引在列上:

In [352]: plantGrowthStackedDF.unstack(level='group').columns
Out[352]: MultiIndex
 [(u'weight', u'ctrl'), (u'weight', u'trt1'), (u'weight', u'trt2')]

让我们看看调用反向操作stack时会发生什么:

In [366]: plantGrowthStackedDF.unstack(level=0).stack('group')
Out[366]:              weight
 observation  group
 1  ctrl   4.17
 trt1   4.81
 trt2   6.31
 2  ctrl   5.58
 trt1   4.17
 trt2   5.12
 3  ctrl   5.18
 trt1   4.41
 trt2   5.54
 4  ctrl   6.11
 trt1   3.59
 trt2   5.50
 ...
 10  ctrl   5.14
 trt1   4.69
 trt2   5.26

在这里,我们看到我们得到的不是原始的堆叠数据帧,因为堆叠级别-即'group'—成为列多重索引中新的最低级别。 在原始堆叠的数据帧中,group是最高级别。 这是对stackunstack的完全可逆的调用序列。 默认情况下,unstack()函数会取消堆叠最后一个级别,即observation,如下所示:

In [370]: plantGrowthStackedDF.unstack()
Out[370]:                                weight
 observation     1   2   3    4   5    6    7    8     9    10
 group
 ctrl        4.17 5.58 5.18 6.11 4.50 4.61 5.17 4.53 5.33 5.14
 trt1        4.81 4.17 4.41 3.59 5.87  3.83 6.03 4.89 4.32 4.69
 trt2        6.31 5.12 5.54 5.50 5.37  5.29 4.92 6.15 5.80 5.26

默认情况下,stack()函数将堆叠级别设置为结果行中的多重索引中的最低级别:

In [369]: plantGrowthStackedDF.unstack().stack()
Out[369]:              weight
 group  observation
 ctrl    1   4.17
 2   5.58
 3   5.18
 ...
 10  5.14
 trt1    1   4.81
 2   4.17
 3   4.41
 ...
 10  4.69
 trt2    1   6.31
 2   5.12
 3   5.54
 ...
 10  5.26

重塑数据帧的其他方法

还有许多其他与重塑数据帧有关的方法。 我们将在这里讨论。

使用melt函数

melt函数使我们能够通过将数据帧的某些列指定为 ID 列来转换它。 这样可以确保在进行任何重要的转换后,它们始终保持为列。 其余的非 ID 列可被视为变量,并可进行透视设置并成为名称-值两列方案的一部分。 ID 列唯一标识数据帧中的一行。

可以通过提供var_namevalue_name参数来自定义那些非 ID 列的名称。 举个例子,也许可以最好地说明melt的使用:

In [385]: from pandas.core.reshape import melt

In [401]: USIndexDataDF[:2]
Out[401]:    TradingDate   Nasdaq    S&P 500   Russell 2000  DJIA
 0   2014/01/30    4123.13   1794.19   1139.36       15848.61
 1   2014/01/31    4103.88   1782.59   1130.88       15698.85

In [402]: melt(USIndexDataDF[:2], id_vars=['TradingDate'], var_name='Index Name', value_name='Index Value')
Out[402]:
 TradingDate   Index Name    Index value
 0  2014/01/30    Nasdaq        4123.13
 1  2014/01/31    Nasdaq        4103.88
 2  2014/01/30    S&P 500       1794.19
 3  2014/01/31    S&P 500       1782.59
 4  2014/01/30    Russell 2000  1139.36
 5  2014/01/31    Russell 2000  1130.88
 6  2014/01/30    DJIA          15848.61
 7  2014/01/31    DJIA          15698.85

pandas.get_dummies()函数

此函数用于将分类变量转换为指标数据帧,该指标本质上是分类变量可能值的真值表。 下面的命令是一个示例:

In [408]: melted=melt(USIndexDataDF[:2], id_vars=['TradingDate'], var_name='Index Name', value_name='Index Value')
 melted
Out[408]:      TradingDate   Index Name    Index Value
 0     2014/01/30    Nasdaq        4123.13
 1     2014/01/31    Nasdaq        4103.88
 2     2014/01/30    S&P 500       1794.19
 3     2014/01/31    S&P 500       1782.59
 4     2014/01/30    Russell 2000  1139.36
5     2014/01/31   Russell 2000   1130.88
6     2014/01/30   DJIA        15848.61
7     2014/01/31   DJIA        15698.85

In [413]: pd.get_dummies(melted['Index Name'])
Out[413]:    DJIA  Nasdaq  Russell 2000  S&P 500
 0   0     1        0            0
 1   0     1        0            0
 2   0     0        0            1
 3   0     0        0            1
 4   0     0        1            0
 5   0     0        1            0
 6   1     0        0            0
 7   1     0        0            0

可以在这个链接中找到上述数据的来源。

总结

在本章中,我们看到了各种方法来重新排列 Pandas 中的数据。 我们可以使用pandas.groupby运算符和groupby对象上的关联方法对数据进行分组。 我们可以使用concatappendmergejoin函数合并和合并SeriesDataFrame对象。 最后,我们可以使用stack/unstackpivot/pivot_table函数重塑和创建pivot表。 这对于显示数据以进行可视化或准备数据以输入其他程序或算法非常有用。

在下一章中,我们将研究一些数据分析中有用的任务,可以应用 Pandas,例如处理时间序列数据以及如何处理数据中的缺失值。

要获得有关这些主题的更多信息,请访问官方文档

六、处理缺失数据,时间序列和 Matplotlib 绘图

在本章中,我们将介绍一些必要的主题,这些主题对于培养使用 Pandas 的专业知识必不可少。 这些主题的知识对于准备数据作为处理数据以进行分析,预测或可视化的程序或代码的输入非常有用。 我们将讨论的主题如下:

  • 处理缺失的数据
  • 处理时间序列和日期
  • 使用matplotlib绘图

到本章结束时,用户应该精通这些关键领域。

处理缺失的数据

数据丢失是指由于某种原因在我们的数据集中显示为 NULL 或 N/A 的数据点; 例如,我们可能有一个时间序列,横跨一个月的所有日历日,显示每天股票的收盘价,而非营业日的收盘价则显示为缺失。 一个损坏的数据示例是财务数据集,该数据集以错误的格式显示了交易的活动日期。 例如,由于数据提供者发生错误,因此使用YYYY-MM-DD而不是YYYYMMDD

对于 Pandas,缺失值通常由 NaN 值表示。

除了本机出现在源数据集中之外,还可以通过诸如重新索引或在时间序列的情况下更改频率之类的操作将缺失值添加到数据集中:

In [84]: import numpy as np
 import pandas as pd
 import matplotlib.pyplot as plt
 %matplotlib inline
In [85]: date_stngs = ['2014-05-01','2014-05-02',
 '2014-05-05','2014-05-06','2014-05-07']
 tradeDates = pd.to_datetime(pd.Series(date_stngs))
In [86]: closingPrices=[531.35,527.93,527.81,515.14,509.96]
In [87]: googClosingPrices=pd.DataFrame(data=closingPrices,
 columns=['closingPrice'],
 index=tradeDates)
 googClosingPrices
Out[87]:                closingPrice
 tradeDates 
 2014-05-01       531.35
 2014-05-02       527.93
 2014-05-05       527.81
 2014-05-06       515.14
 2014-05-07       509.96
 5 rows 1 columns

可以在这里找到上述数据的来源。

Pandas 还提供了一个 API,可以从各种数据提供商(例如 Yahoo)读取股票数据:

In [29]: import pandas.io.data as web
In [32]: import datetime
 googPrices = web.get_data_yahoo("GOOG",
 start=datetime.datetime(2014, 5, 1),
 end=datetime.datetime(2014, 5, 7))
In [38]: googFinalPrices=pd.DataFrame(googPrices['Close'],
 index=tradeDates)
In [39]: googFinalPrices
Out[39]:           Close
 2014-05-01  531.34998
 2014-05-02  527.92999
 2014-05-05  527.81000
 2014-05-06  515.14001
 2014-05-07  509.95999

有关更多详细信息,请参见这里

现在,我们有了一个时间序列,描述了 Google 股票自 2014 年 5 月 1 日至 2014 年 5 月 7 日的收盘价,由于交易只在工作日发生,因此日期范围存在缺口。 如果要更改日期范围以使其显示日历日(即周末),则可以将时间序列索引的频率从工作日更改为日历日,如下所示:

In [90]: googClosingPricesCDays=googClosingPrices.asfreq('D')
 googClosingPricesCDays
Out[90]:    closingPrice
 2014-05-01  531.35
 2014-05-02  527.93
 2014-05-03  NaN
 2014-05-04  NaN
 2014-05-05  527.81
 2014-05-06  515.14
 2014-05-07  509.96
 7 rows 1 columns

请注意,我们现在为 2014 年 5 月 3 日和 2014 年 5 月 4 日的周末日期引入了closingPriceNaN值。

我们可以通过使用isnullnotnull函数来检查缺少的值,如下所示:

In [17]: googClosingPricesCDays.isnull()
Out[17]: closingPrice
 2014-05-01   False
 2014-05-02   False
 2014-05-03   True
 2014-05-04   True
 2014-05-05   False
 2014-05-06   False
 2014-05-07   False
 7 rows 1 columns

In [18]: googClosingPricesCDays.notnull()
Out[18]: closingPrice
 2014-05-01   True
 2014-05-02   True
 2014-05-03   False
 2014-05-04   False
 2014-05-05   True
 2014-05-06   True
 2014-05-07   True
 7 rows 1 columns

在每种情况下都会返回一个布尔型数据帧。 在datetime和 Pandas 时间戳中,缺失值由NaT值表示。 对于基于时间的类型,这相当于 Pandas 中的NaN

In [27]: tDates=tradeDates.copy()
 tDates[1]=np.NaN
 tDates[4]=np.NaN

In [28]: tDates
Out[28]: 0   2014-05-01
 1          NaT
 2   2014-05-05
 3   2014-05-06
 4          NaT
 Name: tradeDates, dtype: datetime64[ns]

In [4]: FBVolume=[82.34,54.11,45.99,55.86,78.5]
 TWTRVolume=[15.74,12.71,10.39,134.62,68.84]

In [5]: socialTradingVolume=pd.concat([pd.Series(FBVolume),
 pd.Series(TWTRVolume),
 tradeDates], axis=1,
 keys=['FB','TWTR','TradeDate'])
 socialTradingVolume
Out[5]:      FB       TWTR    TradeDate
 0   82.34    15.74   2014-05-01
 1   54.11    12.71   2014-05-02
 2   45.99    10.39   2014-05-05
 3   55.86    134.62  2014-05-06
 4   78.50    68.84   2014-05-07
 5 rows × 3 columns

In [6]: socialTradingVolTS=socialTradingVolume.set_index('TradeDate')
socialTradingVolTS
Out[6]:
 TradeDate    FB      TWTR
 2014-05-01   82.34   15.74
 2014-05-02   54.11   12.71
 2014-05-05   45.99   10.39
 2014-05-06   55.86   134.62
 2014-05-07   78.50   68.84
 5 rows × 2 columns

In [7]: socialTradingVolTSCal=socialTradingVolTS.asfreq('D')
 socialTradingVolTSCal
Out[7]:
 FB      TWTR
 2014-05-01  82.34   15.74
 2014-05-02  54.11   12.71
 2014-05-03  NaN     NaN
 2014-05-04  NaN     NaN
 2014-05-05  45.99   10.39
 2014-05-06  55.86   134.62
 2014-05-07  78.50   68.84
 7 rows × 2 columns

我们可以对包含缺失值的数据执行算术运算。 例如,我们可以计算 Facebook 和 Twitter 的两只股票的总交易量(百万股),如下所示:

In [8]: socialTradingVolTSCal['FB']+socialTradingVolTSCal['TWTR']
Out[8]: 2014-05-01     98.08
 2014-05-02     66.82
 2014-05-03       NaN
 2014-05-04       NaN
 2014-05-05     56.38
 2014-05-06    190.48
 2014-05-07    147.34
 Freq: D, dtype: float64

默认情况下,对包含缺失值的对象执行的任何操作都会在该位置返回缺失值,如以下命令所示:

In [12]: pd.Series([1.0,np.NaN,5.9,6])+pd.Series([3,5,2,5.6])
Out[12]: 0     4.0
 1     NaN
 2     7.9
 3    11.6
 dtype: float64
In [13]: pd.Series([1.0,25.0,5.5,6])/pd.Series([3,np.NaN,2,5.6])
Out[13]: 0    0.333333
 1         NaN
 2    2.750000
 3    1.071429
 dtype: float64

但是,NumPy 处理聚合计算的方式与 Pandas 的处理方式有所不同。

在 Pandas 中,默认值是将缺失值视为0并进行聚合计算,而对于 NumPy,如果缺少任何值,则返回NaN。 这是一个例子:

In [15]: np.mean([1.0,np.NaN,5.9,6])
Out[15]: nan

In [16]: np.sum([1.0,np.NaN,5.9,6])
Out[16]: nan

但是,如果此数据在 pandas 序列中,则将获得以下输出:

In [17]: pd.Series([1.0,np.NaN,5.9,6]).sum()
Out[17]: 12.9
In [18]: pd.Series([1.0,np.NaN,5.9,6]).mean()
Out[18]: 4.2999999999999998

重要的是要意识到 Pandas 和 NumPy 在行为上的差异。 但是,如果我们希望 NumPy 的行为与 Pandas 相同,则可以使用np.nanmeannp.nansum函数,如下所示:

In [41]: np.nanmean([1.0,np.NaN,5.9,6])
Out[41]: 4.2999999999999998

In [43]: np.nansum([1.0,np.NaN,5.9,6])
Out[43]: 12.9

有关 NumPy np.nan聚合函数的更多信息,请参考这里

处理缺失值

有多种处理缺失值的方法,如下所示:

  1. 通过使用fillna()函数来填充 NA 值。 这是一个例子:

    In [19]: socialTradingVolTSCal
    Out[19]:    FB   TWTR
     2014-05-01  82.34   15.74
     2014-05-02  54.11   12.71
     2014-05-03  NaN     NaN
     2014-05-04  NaN     NaN
     2014-05-05  45.99   10.39
     2014-05-06  55.86   134.62
     2014-05-07  78.50   68.84
     7 rows × 2 columns
    
    In [20]: socialTradingVolTSCal.fillna(100)
    Out[20]:            FB      TWTR
     2014-05-01   82.34   15.74
     2014-05-02   54.11   12.71
     2014-05-03   100.00  100.00
     2014-05-04   100.00  100.00
     2014-05-05   45.99   10.39
     2014-05-06   55.86   134.62
     2014-05-07   78.50   68.84
     7 rows × 2 columns
    
    

    我们还可以使用ffillbfill参数填充前向或后向值:

    In [23]: socialTradingVolTSCal.fillna(method='ffill')
    Out[23]:            FB      TWTR
     2014-05-01   82.34   15.74
     2014-05-02   54.11   12.71
     2014-05-03   54.11   12.71
     2014-05-04   54.11   12.71
     2014-05-05   45.99   10.39
     2014-05-06   55.86   134.62
     2014-05-07   78.50   68.84
     7 rows × 2 columns
    
    In [24]: socialTradingVolTSCal.fillna(method='bfill')
    Out[24]:            FB      TWTR
     2014-05-01   82.34   15.74
     2014-05-02   54.11   12.71
     2014-05-03   45.99   10.39
     2014-05-04   45.99   10.39
     2014-05-05   45.99   10.39
     2014-05-06   55.86   134.62
     2014-05-07   78.50   68.84
     7 rows × 2 columns
    
    

    pad方法是ffill的替代名称。 有关更多详细信息,您可以转到这里

  2. 通过使用dropna()函数删除/删除缺少值的行和列。 以下是一个示例:

    In [21]: socialTradingVolTSCal.dropna()
    Out[21]:      FB      TWTR
     2014-05-01  82.34   15.74
     2014-05-02  54.11   12.71
     2014-05-05  45.99   10.39
     2014-05-06  55.86   134.62
     2014-05-07  78.50   68.84
     5 rows × 2 columns
    
    
  3. 我们还可以使用interpolate()函数对缺失值进行插值和填充,如以下命令所述:

    In [27]: pd.set_option('display.precision',4)
     socialTradingVolTSCal.interpolate()
    Out[27]:       FB       TWTR
     2014-05-01   82.340   15.740
     2014-05-02   54.110   12.710
     2014-05-03   51.403   11.937
     2014-05-04   48.697   11.163
     2014-05-05   45.990   10.390
     2014-05-06   55.860   134.620
     2014-05-07   78.500   68.840
     7 rows × 2 columns
    
    

    interpolate()函数还采用一个参数 -- 表示该方法的method。 这些方法包括线性,二次,三次样条等等。 您可以从官方文档中获取更多信息。

处理时间序列

在本节中,我们向您展示如何处理时间序列数据。 我们将首先展示如何使用从csv文件中读取的数据创建时间序列数据。

读取时间序列数据

在这里,我们演示了读取时间序列数据的各种方法:

In [7]: ibmData=pd.read_csv('ibm-common-stock-closing-prices-1959_1960.csv')
 ibmData.head()
Out[7]:    TradeDate     closingPrice
 0   1959-06-29   445
 1   1959-06-30   448
 2   1959-07-01   450
 3   1959-07-02   447
 4   1959-07-06   451
 5 rows 2 columns

可以在这个链接中找到此信息的来源。

我们希望TradeDate列是datetime值的序列,以便我们可以为其编制索引并创建时间序列。 让我们首先检查TradeDate序列中值的类型:

In [16]: type(ibmData['TradeDate'])
Out[16]: pandas.core.series.Series
In [12]: type(ibmData['TradeDate'][0])
Out[12]: str

接下来,我们将其转换为Timestamp类型:

In [17]: ibmData['TradeDate']=pd.to_datetime(ibmData['TradeDate'])
 type(ibmData['TradeDate'][0])
Out[17]: pandas.tslib.Timestamp

现在,我们可以将TradeDate列用作索引:

In [113]: #Convert DataFrame to TimeSeries
 #Resampling creates NaN rows for weekend dates, hence use dropna
 ibmTS=ibmData.set_index('TradeDate').resample('D')['closingPrice'].dropna()
 ibmTS
Out[113]: TradeDate
 1959-06-29    445
 1959-06-30    448
 1959-07-01    450
 1959-07-02    447
 1959-07-06    451
 ...
 Name: closingPrice, Length: 255

日期偏移和时间增量对象

DateOffset对象表示时间的变化或偏移。 DateOffset对象的关键特征如下:

  • 可以将其添加到datetime对象或从中减去,以获得转换后的日期
  • 可以乘以一个整数(正数或负数),以便可以多次应用该增量
  • 它具有前滚和后滚方法,可将日期向前移动到下一个偏移日期或向后移动到上一个偏移日期

我们说明了如何使用日期偏移对象,如下所示:

In [371]: xmasDay=pd.datetime(2014,12,25)
 xmasDay
Out[371]: datetime.datetime(2014, 12, 25, 0, 0)

In [373]: boxingDay=xmasDay+pd.DateOffset(days=1)
 boxingDay
Out[373]: Timestamp('2014-12-26 00:00:00', tz=None)

In [390}: today=pd.datetime.now()
 today
Out[390]: datetime.datetime(2014, 5, 31, 13, 7, 36, 440060)

注意,datetime.datetimepd.Timestamp不同。 前者是 Python 类,效率低下,而后者基于numpy.datetime64数据类型。 pd.DateOffset对象与pd.Timestamp一起使用,并将其添加到datetime.datetime函数中可将该对象转换为pd.Timestamp对象。

下面说明了从今天开始一周的命令:

In [392]: today+pd.DateOffset(weeks=1)
Out[392]: Timestamp('2014-06-07 13:07:36.440060', tz=None)

下图说明了从现在起五年内的命令:

In [394]: today+2*pd.DateOffset(years=2, months=6)
Out[394]: Timestamp('2019-05-30 13:07:36.440060', tz=None)

这是使用rollforward函数的示例。 QuarterBegin是一个DateOffset对象,用于将给定的datetime对象增加到下一个日历季度的开始:

In [18]: lastDay=pd.datetime(2013,12,31)
In [24]: from pandas.tseries.offsets import QuarterBegin
 dtoffset=QuarterBegin()
 lastDay+dtoffset
Out[24]: Timestamp('2014-03-01 00:00:00', tz=None)

In [25]: dtoffset.rollforward(lastDay)
Out[25]: Timestamp('2014-03-01 00:00:00', tz=None)

因此,我们可以看到,2013 年 12 月 31 日之后的下一个季度从 2014 年 3 月 1 日开始。TimedeltasDateOffsets相似,但可用于datetime.datetime对象。 以下命令解释了它们的使用:

In [40]: weekDelta=datetime.timedelta(weeks=1)
 weekDelta
Out[40]: datetime.timedelta(7)

In [39]: today=pd.datetime.now()
 today
Out[39]: datetime.datetime (2014, 6, 2, 3, 56, 0, 600309)

In [41]: today+weekDelta
Out[41]: datetime.datetime (2014, 6, 9, 3, 56,0, 600309)

与时间序列相关的实例方法

在本节中,我们探索用于时间序列对象的各种方法,例如移位,频率转换和重采样。

平移/滞后

有时,我们可能希望将时间序列中的值在时间上向后或向前移动。 一种可能的情况是,数据集包含公司中去年新雇员的开始日期列表,并且公司的人力资源计划希望将这些日期提前一年,以便可以激活雇员的福利。 我们可以通过使用shift()函数来做到这一点,如下所示:

In [117]: ibmTS.shift(3)
Out[117]: TradeDate
 1959-06-29    NaN
 1959-06-30    NaN
 1959-07-01    NaN
 1959-07-02    445
 1959-07-06    448
 1959-07-07    450
 1959-07-08    447
 ...

这将转换所有日历日。 但是,如果我们只希望转移工作日,则必须使用以下命令:

In [119]: ibmTS.shift(3, freq=pd.datetools.bday)
Out[119]: TradeDate
 1959-07-02    445
 1959-07-03    448
 1959-07-06    450
 1959-07-07    447
 1959-07-09    451

在前面的代码片段中,我们指定了freq参数进行平移; 这告诉函数仅更改工作日。 shift函数具有freq参数,其值可以是DateOffset类,类似于timedelta的对象或偏移别名。 因此,使用ibmTS.shift(3, freq='B')也将产生相同的结果。

更改频率

我们可以使用asfreq函数来更改频率,如下所述:

In [131]: # Frequency conversion using asfreq
 ibmTS.asfreq('BM')
Out[131]: 1959-06-30    448
 1959-07-31    428
 1959-08-31    425
 1959-09-30    411
 1959-10-30    411
 1959-11-30    428
 1959-12-31    439
 1960-01-29    418
 1960-02-29    419
 1960-03-31    445
 1960-04-29    453
 1960-05-31    504
 1960-06-30    522
 Freq: BM, Name: closingPrice, dtype: float64

在这种情况下,我们仅从ibmTS时间序列中获取与该月的最后一天相对应的值。 在此,bm代表营业月结束频率。 有关所有可能的频率别名的列表,请访问这里

如果我们指定的频率小于数据的粒度,则间隙将用NaN值填充:

In [132]: ibmTS.asfreq('H')
Out[132]: 1959-06-29 00:00:00    445
 1959-06-29 01:00:00    NaN
 1959-06-29 02:00:00    NaN
 1959-06-29 03:00:00    NaN
 ...
 1960-06-29 23:00:00    NaN
 1960-06-30 00:00:00    522
 Freq: H, Name: closingPrice, Length: 8809

我们也可以将asfreq方法应用于PeriodPeriodIndex对象,类似于我们对datetimeTimestamp对象所做的操作。 PeriodPeriodIndex稍后介绍,用于表示时间间隔。

asfreq方法接受一个方法参数,该参数允许您向前填充(ffill)或向后填充空白,类似于fillna

In [140]: ibmTS.asfreq('H', method='ffill')
Out[140]: 1959-06-29 00:00:00    445
 1959-06-29 01:00:00    445
 1959-06-29 02:00:00    445
 1959-06-29 03:00:00    445
 ...
 1960-06-29 23:00:00    522
 1960-06-30 00:00:00    522
 Freq: H, Name: closingPrice, Length: 8809

数据重采样

TimeSeries.resample函数使我们能够基于采样间隔和采样函数来聚合/聚合更多粒度数据。

下采样是源自数字信号处理的术语,是指降低信号的采样率的过程。 对于数据,我们使用它来减少我们希望处理的数据量。

相反的过程是上采样,该过程用于增加要处理的数据量,并且需要进行插值以获得中间数据点。 有关下采样和上采样的更多信息,请参考上采样和下采样的实际应用用于视觉表示的下采样时间序列

在这里,我们检查了一些滴答数据以用于重采样。 在检查数据之前,我们需要进行准备。 通过这样做,我们将学习一些有关时间序列数据的有用技术,如下所示:

  • 时间戳
  • 时区处理

这是一个使用滴答数据作为 2014 年 5 月 27 日星期二的 Google 股票价格的示例:

In [150]: googTickData=pd.read_csv('./GOOG_tickdata_20140527.csv')
In [151]: googTickData.head()
Out[151]:     Timestamp   close    high    low   open   volume
 0    1401197402  555.008 556.41  554.35 556.38   81100
 1    1401197460  556.250 556.30  555.25 555.25   18500
 2    1401197526  556.730 556.75  556.05 556.39   9900
 3    1401197582  557.480 557.67  556.73 556.73   14700
 4    1401197642  558.155 558.66  557.48 557.59   15700
 5 rows 6 columns

可以在这个链接中找到先前数据的源。

从上一节中可以看到,我们有一个“时间戳”列,以及收盘价,最高价,最低价和开盘价以及 Google 股票交易量的列。

那么,为什么“时间戳”列看起来有点奇怪? 好吧,滴答数据时间戳通常以纪元时间表示(有关更多信息,请参考这里),作为一种更紧凑的存储方式。 我们需要将其转换为更易于理解的时间,我们可以按照以下步骤进行操作:

In [201]: googTickData['tstamp']=pd.to_datetime(googTickData['Timestamp'],unit='s',utc=True)

In [209]: googTickData.head()
Out[209]:
 Timestamp   close   high   low    open   volume tstamp
 0  14011974020 555.008 556.41 554.35 556.38 81100 2014-05-27 13:30:02
 1  1401197460  556.250 556.30 555.25 555.25 18500 2014-05-27 13:31:00
 2  1401197526  556.730 556.75 556.05 556.39 9900  2014-05-27 13:32:06
 3  1401197582  557.480 557.67 556.73 556.73 14700 2014-05-27 13:33:02
 4  1401197642  558.155 558.66 557.48 557.59 15700 2014-05-27 13:34:02
 5 rows 7 columns

现在,我们想将tstamp列作为索引,并消除纪元Timestamp列:

In [210]: googTickTS=googTickData.set_index('tstamp')
 googTickTS=googTickTS.drop('Timestamp',axis=1)
 googTickTS.head()
Out[210]: 
 tstamp                 close    high    low     open     volume
 2014-05-27 13:30:02    555.008  556.41  554.35  556.38   811000
 2014-05-27 13:31:00    556.250  556.30  555.25  555.25   18500
 2014-05-27 13:32:06    556.730  556.75  556.05  556.39   9900
 2014-05-27 13:33:02    557.480  557.67  556.73  556.73   14700
 2014-05-27 13:34:02    558.155  558.66  557.48  557.59   15700
 5 rows 5 columns

请注意,tstamp索引列的时间以 UTC 为单位,我们可以使用tz_localizetz_convert这两个运算符将其转换为美国/东部时间:

In [211]: googTickTS.index=googTickTS.index.tz_localize('UTC').tz_convert('US/Eastern')

In [212]: googTickTS.head()
Out[212]: 
 tstamp                     close    high    low     open   volume 
 2014-05-27 09:30:02-04:00  555.008  556.41  554.35  556.38  81100
 2014-05-27 09:31:00-04:00  556.250  556.30  555.25  555.25  18500
 2014-05-27 09:32:06-04:00  556.730  556.75  556.05  556.39   9900
 2014-05-27 09:33:02-04:00  557.480  557.67  556.73  556.73  14700
 2014-05-27 09:34:02-04:00  558.155  558.66  557.48  557.59  15700
 5 rows 5 columns

In [213]: googTickTS.tail()
Out[213]:
 tstamp                       close     high   low    open    volume
 2014-05-27 15:56:00-04:00    565.4300  565.48 565.30 565.385  14300
 2014-05-27 15:57:00-04:00    565.3050  565.46 565.20 565.400  14700
 2014-05-27 15:58:00-04:00    565.1101  565.31 565.10 565.310  23200
 2014-05-27 15:59:00-04:00    565.9400  566.00 565.08 565.230  55600
 2014-05-27 16:00:00-04:00    565.9500  565.95 565.95 565.950 126000
 5 rows 5 columns

In [214]: len(googTickTS)
Out[214]: 390

从前面的输出中,我们可以看到交易日中每分钟的滴答声-从股市开盘的上午 9:30 到闭市的下午 4:00。 由于在上午 9:30 和下午 4:00 之间有 390 分钟的时间,因此该数据集中有 390 行。

假设我们要每 5 分钟而不是每分钟获取一次快照? 我们可以通过如下使用降采样来实现:

In [216]: googTickTS.resample('5Min').head(6)
Out[216]:           close      high   low    open       volume    tstamp
2014-05-27 09:30:00-04:00 556.72460 557.15800 555.97200 556.46800 27980
2014-05-27 09:35:00-04:00 556.93648 557.64800 556.85100 557.34200  24620
2014-05-27 09:40:00-04:00 556.48600 556.79994 556.27700 556.60678   8620
2014-05-27 09:45:00-04:00 557.05300 557.27600 556.73800 556.96600   9720
2014-05-27 09:50:00-04:00  556.66200  556.93596  556.46400  556.80326  14560
2014-05-27 09:55:00-04:00  555.96580  556.35400  555.85800  556.23600  12400
6 rows 5 columns

用于重采样的默认函数是平均值。 但是,我们还可以指定其他函数,例如最小值,并且可以通过how参数进行重新采样:

In [245]: googTickTS.resample('10Min', how=np.min).head(4)
Out[245]:         close   high      low  open  volume
tstamp
2014-05-27 09:30:00-04:00   555.008  556.3000  554.35  555.25   9900
2014-05-27 09:40:00-04:00   556.190  556.5600  556.13  556.35   3500
2014-05-27 09:50:00-04:00   554.770  555.5500  554.77  555.55   3400
2014-05-27 10:00:00-04:00   554.580  554.9847  554.45  554.58   1800

可以将各种函数名称传递给how参数,例如sumohlcmaxminstdmeanmedianfirstlast

ohlc函数根据时间序列数据返回开-高-低-关值; 第一个,最大,最小和最后一个值。 要指定关闭左间隔还是右间隔,我们可以按以下方式传递closed参数:

In [254]: pd.set_option('display.precision',5)
 googTickTS.resample('5Min', closed='right').tail(3)
Out[254]:                   close     high  low     open       volume
tstamp
2014-05-27 15:45:00-04:00   564.3167  564.3733   564.1075  564.1700  12816.6667
2014-05-27 15:50:00-04:00   565.1128  565.1725   565.0090  565.0650  13325.0000
2014-05-27 15:55:00-04:00   565.5158  565.6033   565.3083  565.4158  40933.3333
3 rows 5 columns

因此,在前面的命令中,我们可以看到最后一行在 15:55 而不是 16:00 处显示了滴答声。

对于上采样,我们需要指定一种填充方法,以确定如何通过fill_method参数填充间隙:

In [263]: googTickTS[:3].resample('30s', fill_method='ffill')
Out[263]:     close    high     low    open  volume    tstamp
 2014-05-27 09:30:00-04:00   555.008  556.41  554.35  556.38   81100
 2014-05-27 09:30:30-04:00   555.008  556.41  554.35  556.38   81100
 2014-05-27 09:31:00-04:00   556.250  556.30  555.25  555.25   18500
 2014-05-27 09:31:30-04:00   556.250  556.30  555.25  555.25   18500
 2014-05-27 09:32:00-04:00   556.730  556.75  556.05  556.39   9900
 5 rows 5 columns

In [264]: googTickTS[:3].resample('30s', fill_method='bfill')
Out[264]:
 close     high    low  open  volume     tstamp
 2014-05-27 09:30:00-04:00  555.008   556.41  554.35  556.38   81100
 2014-05-27 09:30:30-04:00  556.250   556.30  555.25  555.25   18500
 2014-05-27 09:31:00-04:00  556.250   556.30  555.25  555.25   18500
 2014-05-27 09:31:30-04:00  556.730   556.75  556.05  556.39   9900
 2014-05-27 09:32:00-04:00  556.730   556.75  556.05  556.39   9900
 5 rows 5 columns

不幸的是,fill_method参数当前仅支持两种方法-前向填充和后向填充。 插值方法将很有价值。

时间序列频率的别名

要指定偏移量,可以使用许多别名。 一些最常用的方法如下:

  • B, BM:代表工作日,工作月。 这些是一个月的工作日,即不是假日或周末的任何一天。
  • D, W, M, Q, A:代表日历日,周,月,季度,年末。
  • H, T, S, L, U:代表小时,分钟,秒,毫秒和微秒。

这些别名也可以组合。 在以下情况下,我们每 7 分钟 30 秒重新采样一次:

In [267]: googTickTS.resample('7T30S').head(5)
Out[267]:
 close     high   low   open    volume 
tstamp
2014-05-27 09:30:00-04:00 556.8266 557.4362 556.3144 556.8800 28075.0
2014-05-27 09:37:30-04:00 556.5889 556.9342 556.4264 556.7206 11642.9
2014-05-27 09:45:00-04:00 556.9921 557.2185 556.7171 556.9871  9800.0
2014-05-27 09:52:30-04:00 556.1824 556.5375 556.0350 556.3896 14350.0
2014-05-27 10:00:00-04:00 555.2111 555.4368 554.8288 554.9675 12512.5
5 rows x 5 columns

可以将后缀应用于频率别名,以指定在频率周期中何时开始。 这些称为锚定偏移量:

  • W-SUN, MON, ... DEC,例如,W-TUE表示从星期二开始的每周频率。
  • Q-JAN, FEB, ... DEC,例如,Q-MAY表示每年 5 月底的季度频率。
  • A-JAN, FEB, ... DEC,例如,A-MAY表示每年的频率,到 5 月结束。

这些偏移量可用作date_rangebdate_range函数的参数,以及用作PeriodIndexDatetimeIndex等索引类型的构造器。 可以在 Pandas 文档 中找到对此的全面讨论。

时间序列的概念和数据类型

处理时间序列时,必须考虑两个主要概念:时间点和范围或时间跨度。 在 Pandas 中,前者由时间戳数据类型表示,该数据类型等效于 Python 的datatime.datetimedatetime)数据类型,并且可以互换。 后者(时间跨度)由时间段数据类型表示,该数据类型特定于 Pandas。

这些数据类型均具有与之关联的索引数据类型:Timestamp / DatetimeDatetimeIndexPeriodPeriodIndex。 这些索引数据类型基本上是numpy.ndarray的子类型,包含对应的时间戳和时间段数据类型,并且可用作序列和数据帧对象的索引。

时间段和时间段索引

Period数据类型用于表示时间范围或时间跨度。 这里有一些例子:

# Period representing May 2014
In [287]: pd.Period('2014', freq='A-MAY')
Out[287]: Period('2014', 'A-MAY')

# Period representing specific day – June 11, 2014
In [292]: pd.Period('06/11/2014')
Out[292]: Period('2014-06-11', 'D')

# Period representing 11AM, Nov 11, 1918 
In [298]: pd.Period('11/11/1918 11:00',freq='H')
Out[298]: Period('1918-11-11 11:00', 'H')

我们可以向Periods添加整数,以使时间段提前所需的频率单位数:

In [299]: pd.Period('06/30/2014')+4
Out[299]: Period('2014-07-04', 'D')

In [303]: pd.Period('11/11/1918 11:00',freq='H') - 48
Out[303]: Period('1918-11-09 11:00', 'H')

我们还可以计算两个Periods之间的差,并返回它们之间的频率单位数:

In [304]: pd.Period('2014-04', freq='M')-pd.Period('2013-02', freq='M')
Out[304]: 14

时间段索引

可以通过两种方式创建PeriodIndex对象,该对象是Period对象的index类型。

  1. 使用period_range函数从一些period对象中,类似于date_range

    In [305]: perRng=pd.period_range('02/01/2014','02/06/2014',freq='D')
     perRng
    Out[305]: <class 'pandas.tseries.period.PeriodIndex'>
     freq: D
     [2014-02-01, ..., 2014-02-06]
     length: 6
    
    In [306]: type(perRng[:2])
    Out[306]: pandas.tseries.period.PeriodIndex
    
    In [307]: perRng[:2]
    Out[307]: <class 'pandas.tseries.period.PeriodIndex'>
     freq: D
     [2014-02-01, 2014-02-02]
    
    

    正如我们从前面的命令可以确认的那样,当您拉开盖子时,PeriodIndex函数实际上就是下面的Period对象的ndarray

  2. 也可以通过直接调用Period构造器来完成:

    In [312]: JulyPeriod=pd.PeriodIndex(['07/01/2014','07/31/2014'], freq='D')
     JulyPeriod
    Out[312]: <class 'pandas.tseries.period.PeriodIndex'>
     freq: D
     [2014-07-01, 2014-07-31]
    
    

从前面的输出中可以看出,这两种方法之间的差异是period_range填充了结果ndarray,但是Period构造器没有填充,您必须指定索引中应该包含的所有值。

时间序列数据类型之间的转换

我们可以通过to_periodto_timestamp函数将PeriodPeriodIndex数据类型转换为Datetime / TimestampDatetimeIndex数据类型,如下所示:

In [339]: worldCupFinal=pd.to_datetime('07/13/2014', 
 errors='raise')
 worldCupFinal
 Out[339]: Timestamp('2014-07-13 00:00:00')

In [340]: worldCupFinal.to_period('D')
 Out[340]: Period('2014-07-13', 'D')

In [342]: worldCupKickoff=pd.Period('06/12/2014','D')
 worldCupKickoff
Out[342]: Period('2014-06-12', 'D')
In [345]: worldCupKickoff.to_timestamp()
Out[345]: Timestamp('2014-06-12 00:00:00', tz=None)

In [346]: worldCupDays=pd.date_range('06/12/2014',periods=32, 
 freq='D')
 worldCupDays
Out[346]: <class 'pandas.tseries.index.DatetimeIndex'>
 [2014-06-12, ..., 2014-07-13]
 Length: 32, Freq: D, Timezone: None

In [347]: worldCupDays.to_period()
Out[347]: <class 'pandas.tseries.period.PeriodIndex'>
 freq: D
 [2014-06-12, ..., 2014-07-13]
 length: 32

与时间序列相关的对象的摘要

下表总结了与时间序列有关的对象:

对象 摘要
datetime.datetime 这是一个标准的 Python datetime
Timestamp 这是源自datetime.datetime的 Pandas 类
DatetimeIndex 这是一个 Pandas 类,实现为Timestamp / datetime对象的不可变numpy.ndarray
Period 这是代表某个时间段的 Pandas 类
PeriodIndex 这是一个 Pandas 类,实现为Period对象的不可变numpy.ndarray
timedelta 这是一个 Python 类,表示两个datetime.datetime实例之间的差异。 实现为datetime.timedelta
relativedelta 实现为dateutil.relativedeltadateutil是标准 Python datetime模块的扩展。 它提供了额外的功能,例如以大于 1 天的单位表示的时间增量。
DateOffset 这是一个表示常规频率增量的 Pandas 类。 它具有与dateutil.relativedelta相似的功能。

matplotlib 绘图

本节简要介绍了如何使用matplotlib在 Pandas 中进行绘图。 matplotlib api使用标准约定导入,如以下命令所示:

In [1]: import matplotlib.pyplot as plt

序列和数据帧有一个plot方法,它只是plt.plot的包装。 在这里,我们将研究如何绘制正弦和余弦函数的简单图。 假设我们希望在-pipi间隔上绘制以下函数:

  • f(x) = cos(x) + sin(x)
  • g(x) = cos(x) - sin(x)

这给出了以下间隔:

In [51]: import numpy as np
In [52]: X = np.linspace(-np.pi, np.pi, 256,endpoint=True)

In [54]: f,g = np.cos(X)+np.sin(X), np.sin(X)-np.cos(X)
In [61]: f_ser=pd.Series(f)
 g_ser=pd.Series(g)

In [31]: plotDF=pd.concat([f_ser,g_ser],axis=1)
 plotDF.index=X
 plotDF.columns=['sin(x)+cos(x)','sin(x)-cos(x)']
 plotDF.head()
Out[31]:  sin(x)+cos(x)  sin(x)-cos(x)
-3.141593  -1.000000   1.000000
-3.116953  -1.024334   0.975059
-3.092313  -1.048046   0.949526
-3.067673  -1.071122   0.923417
-3.043033  -1.093547   0.896747
5 rows × 2 columns

现在,我们可以使用plot()命令和plt.show()命令绘制数据帧来显示它:

In [94]: plotDF.plot()
 plt.show()

We can apply a title to the plot as follows:
In [95]: plotDF.columns=['f(x)','g(x)']
 plotDF.plot(title='Plot of f(x)=sin(x)+cos(x), \n g(x)=sinx(x)-cos(x)')
 plt.show()

以下是上述命令的输出:

Plotting using matplotlib

我们还可以使用以下命令在不同的子图中分别绘制两个序列(函数):

In [96]: plotDF.plot(subplots=True, figsize=(6,6))
 plt.show()

The following is the output of the preceding command:

Plotting using matplotlib

在 Pandas 中使用matplotlib的绘图函数还有很多。 有关更多信息,请参阅这个链接中的文档。

总结

总而言之,我们讨论了如何处理缺失的数据值以及如何处理 Pandas 中的日期和时间序列。 我们还走了一段简短的弯路,以研究matplotlib在 Pandas 中的绘图功能。 在准备用于分析和预测的干净数据时,处理缺失的数据起着非常重要的作用,而绘制和可视化数据的能力是每个好的数据分析人员工具箱中必不可少的部分。

在下一章中,我们将对真实数据集进行一些基本数据分析,在其中我们将分析并回答有关数据的基本问题。 有关 Pandas 中这些主题的更多参考,请查看官方文档

七、统计之旅 -- 经典方法

在本章中,我们简要介绍了经典统计数据(也称为常客方法),并展示了如何将 Pandas 与scipy.statsstatsmodelsstats包一起使用来进行统计分析。 本章及其后的内容并不打算作为统计学的入门书,而只是作为与stats包一起使用 Pandas 的例证。 在下一章中,我们将研究经典观点的另一种方法 -- 贝叶斯统计。 本章讨论的各种主题如下:

  • 描述统计和推论统计
  • 集中趋势和变异性的度量
  • 统计假设检验
  • Z 检验
  • T 检验
  • 方差分析
  • 置信区间
  • 相关和线性回归

描述性统计与推断性统计

在描述性统计或摘要统计中,我们尝试以定量的方式描述数据集合的特征。 这与推论统计或归纳统计不同,因为其目的是对样本进行聚合,而不是使用数据来推断或得出有关样本来源人群的结论。

集中趋势和变异性的度量

描述统计中使用的一些度量包括集中趋势度量和变异性度量。

集中趋势的度量是单个值,它通过指定数据内的中心位置来尝试描述数据集。 集中趋势的三个最常见度量是平均值中位数众数

可变性的度量用于描述数据集中的可变性。 可变性的度量包括方差和标准差。

集中趋势的度量

让我们看一下集中趋势的度量,并在以下各节中进行说明。

均值

均值或样本是最流行的集中趋势度量。 它等于数据集中所有值的总和除以数据集中值的数量。 因此,在 n 个值的数据集中,均值计算如下:

The mean

如果数据值来自样本,则使用 The mean,如果数据值来自总体,则使用μ。

样本平均值和总体平均值不同。 样本均值是所谓的真实总体均值的无偏估计量。 通过对总体进行重复随机抽样以计算样本均值,我们可以获得样本均值的均值。 然后,我们可以调用大数定律和中心极限定理CLT),并将样本均值的平均值表示为真实总体均值的估计值。

人口平均值也称为人口的期望值。

作为计算值的平均值通常不是数据集中观察到的值之一。 使用均值的主要缺点是,极易受到离群值的影响,或者数据集非常偏斜。 有关其他信息,请参阅以下链接:http://en.wikipedia.org/wiki/Sample_mean_and_sample_covariancehttp://en.wikipedia.org/wiki/Law_of_large_numbershttp://bit.ly/1bv7l4s

中位数

中位数是将已排序数据值的集合分为两半的数据值。 它的人口正好在其左侧,而另一半则在其右侧。 在数据集中的值数为偶数的情况下,中位数是两个中间值的平均值。 它受异常值和偏斜数据的影响较小。

众数

众数是数据集中最频繁出现的值。 它通常用于类别数据,以便知道最常见的类别。 使用该众数的一个缺点是它不是唯一的。 具有两种众数的分布称为双峰分布,而具有多种众数的分布称为多峰分布。 这是一个双峰分布的示意图,其中众数分别为两个和七个,因为它们在数据集中都出现了四次:

In [4]: import matplotlib.pyplot as plt
 %matplotlib inline 
In [5]: plt.hist([7,0,1,2,3,7,1,2,3,4,2,7,6,5,2,1,6,8,9,7])
 plt.xlabel('x')
 plt.ylabel('Count')
 plt.title('Bimodal distribution')
 plt.show()

The mode

用 Python 计算数据集中趋势的度量

为了说明这一点,让我们考虑以下数据集,该数据集由 15 个学生在 20 分中得到的分数获得:

In [18]: grades = [10, 10, 14, 18, 18, 5, 10, 8, 1, 12, 14, 12, 13, 1, 18]

均值,中位数和众数可以如下获得:

In [29]: %precision 3  # Set output precision to 3 decimal places
Out[29]:u'%.3f'

In [30]: import numpy as np
 np.mean(grades)
Out[30]: 10.933

In [35]: %precision
 np.median(grades)
Out[35]: 12.0

In [24]: from scipy import stats
 stats.mode(grades)
Out[24]: (array([ 10.]), array([ 3.]))
In [39]: import matplotlib.pyplot as plt
In [40]: plt.hist(grades)
 plt.title('Histogram of grades')
 plt.xlabel('Grade')
 plt.ylabel('Frequency')
 plt.show()

Computing measures of central tendency of a dataset in Python

为了说明数据的偏斜度或离群值会如何严重影响均值作为衡量集中趋势的效用,请考虑以下数据集,该数据集显示了工厂员工的工资(以千美元计):

In [45]: %precision 2
 salaries = [17, 23, 14, 16, 19, 22, 15, 18, 18, 93, 95]

In [46]: np.mean(salaries)
Out[46]: 31.82

基于平均值,我们可以假设数据以31.82的平均值为中心。 但是,我们会错的。 为了看到这一点,让我们使用条形图显示数据的经验分布:

In [59]: fig = plt.figure()
 ax = fig.add_subplot(111)
 ind = np.arange(len(salaries))
 width = 0.2
 plt.hist(salaries, bins=xrange(min(salaries),
 max(salaries)).__len__())
 ax.set_xlabel('Salary')
 ax.set_ylabel('# of employees')
 ax.set_title('Bar chart of salaries')
 plt.show()

Computing measures of central tendency of a dataset in Python

从前面的条形图中,我们可以看到大多数薪水远低于 30K,并且没有人接近 32K 的平均值。 现在,如果我们看一下中位数,我们会发现在这种情况下,它是衡量集中趋势的更好方法:

In [47]: np.median(salaries)
Out[47]: 18.00

我们还可以看一下数据的直方图:

In [56]: plt.hist(salaries, bins=len(salaries))
 plt.title('Histogram of salaries')
 plt.xlabel('Salary')
 plt.ylabel('Frequency')
 plt.show()

Computing measures of central tendency of a dataset in Python

注意

直方图实际上是数据的更好表示,因为条形图通常用于表示类别数据,而直方图是定量数据的首选,而薪金数据就是这种情况。

有关何时使用直方图和条形图的更多信息,请参见这里

如果分布是对称的且是单峰的(即只有一种众数),则三个度量(均值,中位数和众数)将相等。 如果分配偏斜则不是这种情况。 在这种情况下,均值和中位数会彼此不同。 对于负偏斜分布,对于正偏斜分布,均值将低于中位数,反之亦然:

Computing measures of central tendency of a dataset in Python

上图来自这里

度量值的变异性,分散性或差距

我们在描述性统计中测量的分布的另一个特征是可变性。

可变性指定数据点彼此不同或分散多少。 可变性度量很重要,因为它们可以提供对集中趋势度量无法提供的数据性质的洞察。

例如,假设我们进行了一项研究,以检验学前教育计划在提高经济弱势儿童的考试成绩方面的效果。 我们不仅可以通过整个样本的检验分数的平均值来衡量有效性,而且可以通过分数的分散性来衡量有效性。 它对某些学生有用,而对其他学生却没有那么多? 数据的可变性可以帮助我们确定要采取的一些步骤,以提高程序的实用性。

范围

色散的最简单度量是范围。 范围是数据集中最低和最高得分之间的差。 这是传播最简单的方法。

范围=最大值-最小值

四分位数

四分位数和相关的四分位数范围是分散的更重要度量。 它也代表四分位数,这意味着它是度量标准上的值,低于该值时已排序数据集中的分数的 25%,50%,75% 和 100% 下降。 四分位数是将数据集分为四组的三个点,每个组包含四分之一的数据。 为了说明这一点,假设我们有一个包含 20 个检验成绩的数据集,其中对它们的排名如下:

In [27]: import random
 random.seed(100)
 testScores = [random.randint(0,100) for p in 
 xrange(0,20)]
 testScores
Out[27]: [14, 45, 77, 71, 73, 43, 80, 53, 8, 46, 4, 94, 95, 33, 31, 77, 20, 18, 19, 35]

In [28]: #data needs to be sorted for quartilessortedScores = np.sort(testScores) 
In [30]: rankedScores = {i+1: sortedScores[i] for i in 
 xrange(len(sortedScores))}

In [31]: rankedScores
Out[31]:
{1: 4,
 2: 8,
 3: 14,
 4: 18,
 5: 19,
 6: 20,
 7: 31,
8: 33,
 9: 35,
 10: 43,
 11: 45,
 12: 46,
 13: 53,
 14: 71,
 15: 73,
 16: 77,
 17: 77,
 18: 80,
 19: 94,
 20: 95}

第一个四分位数(Q1)在第五和第六个分数之间,第二个四分位数(Q2)在第十和第十一个分数之间,第三个四分位数在第十五和第十六个分数之间。 因此,我们有了(通过使用线性插值并计算中点):

Q1 = (19+20)/2 = 19.5
Q2 = (43 + 45)/2 = 44
Q3 = (73 + 77)/2 = 75

要在 IPython 中看到这一点,我们可以使用scipy.statsnumpy.percentile包:

In [38]: from scipy.stats.mstats import mquantiles
 mquantiles(sortedScores)
Out[38]: array([ 19.45,  44\.  ,  75.2 ])

In [40]: [np.percentile(sortedScores, perc) for perc in [25,50,75]]
Out[40]: [19.75, 44.0, 74.0]

值与我们先前的计算不完全匹配的原因是由于插值方法不同。 可在这个链接中找到有关获取四分位数值的各种方法的更多信息。 四分位数范围是从第三四分位数(Q3-Q1)中减去的第一四分位数,它表示数据集中的中位数 50。

有关更多信息,请参见这里

有关scipy.statsnumpy.percentile函数的更多详细信息,请参见这个文档这个文档

偏差和方差

讨论可变性的基本思想是偏差的概念。 简而言之,偏差测度告诉我们给定值与分布平均值 Deviation and variance 的距离有多远。

为了找到一组值的偏差,我们将方差定义为偏差平方的总和,然后将其除以数据集的大小对其进行归一化。 这称为方差。 由于负偏差和正偏差彼此抵消,因此我们需要使用偏差的平方和作为围绕均值结果的偏差之和。 偏差的平方和定义如下:

Deviation and variance

可以证明前面的表达式等效于:

Deviation and variance

正式地,方差定义如下:

  • 对于样本方差,请使用以下公式:

    Deviation and variance

  • 对于总体方差,使用以下公式:

    Deviation and variance

对于样本方差而不是 Deviation and variance,分母为 Deviation and variance 的原因是,对于样本方差,我们希望使用无偏估计量。 有关更多信息,请查看这里

此度量的值以平方单位表示。 这强调了一个事实,我们计算出的方差是平方偏差。 因此,要获得与数据集原始点相同单位的偏差,我们必须取平方根,这就是我们所说的标准差。 因此,使用以下公式可以得出样品的标准差:

Deviation and variance

但是,对于总体,标准差由以下公式给出:

Deviation and variance

假设检验 -– 原假设和替代假设

在上一节中,我们简要介绍了所谓的描述性统计。 在本节中,我们将讨论所谓的推论统计,由此我们尝试使用样本数据集的特征来得出有关更广泛总体的结论。

推论统计中最重要的方法之一就是假设检验。 在假设检验中,我们尝试确定某个假设或研究问题在一定程度上是否正确。 一种假设的例子是:吃菠菜可以改善长期记忆。

为了通过假设检验调查此问题,我们可以选择一组人作为我们的研究对象,并将其分为两组或样本。 第一组是实验组,它将在预定的时间内吃菠菜。 不接受菠菜的第二组为对照组。 在选定的时间段内,将对两组中的个人记忆进行测量和统计。

我们在实验结束时的目标是能够做出这样的陈述,例如“吃菠菜可以改善长期记忆,而这并非偶然”。 这也称为重要性。

在前面的场景中,研究中的主题集合称为样本,而我们希望得出结论的一般人群是人口。

我们研究的最终目标是确定我们在样本中观察到的任何影响是否可以推广到整个人群。 为了进行假设检验,我们将需要提出原假设和替代假设。

原假设和替代假设

通过参考前面的菠菜示例,原假设为:吃菠菜对长期记忆性能没有影响。

原假设就是,它使我们试图通过运行实验来证明所证明的意图无效。 它通过断言某个统计指标(稍后将进行解释)为零来做到这一点。

替代假设是我们希望支持的假设。 这与原假设相反,我们假设它是正确的,直到数据提供了足够的证据表明相反。 因此,在这种情况下,我们的另一种假设是:吃菠菜可改善长期记忆。

象征性地,原假设称为 H0 ,替代假设称为 H1 。 您可能希望将先前的原假设和替代假设重新陈述为对我们的研究更为具体和可衡量的内容。 例如,我们可以按以下方式重铸 H0

每天吃 40 克菠菜 90 天的 1,000 名受试者的平均记忆评分与同期未食用菠菜的 1,000 名受试者的对照组没有什么不同。

在进行实验/研究时,我们专注于试图证明或否定原假设。 这是因为我们可以计算结果归因于偶然性的概率。 但是,没有一种简单的方法来计算替代假设的可能性,因为长期记忆的任何改善都可能是由于吃菠菜以外的其他因素引起的。

我们通过假设原假设为真来检验原假设,并单独计算偶然获得的结果的概率。 我们设置一个阈值水平 -- α -- 对于该阈值水平,如果计算的概率较小,则可以拒绝原假设,如果阈值级别较大,则可以接受。 拒绝原假设等于接受替代假设,反之亦然。

alpha 和 p 值

为了进行实验以决定是否支持我们的原假设,我们需要提出一种方法,使我们能够以具体且可衡量的方式做出决定。 要进行此显着性检验,我们必须考虑两个数字 -- 检验统计量的 p 值和显着性阈值水平,也称为 alpha

如果我们通过假设无效假设为真或仅凭偶然发生而观察到的结果,则 p 值为概率。

假设无效假设为真,则 p 值也可以认为是获得检验统计量的概率与实际获得的检验统计量一样极端或更高。

alpha 值是我们与 p 值进行比较的阈值。 这为我们接受或拒绝原假设提供了一个切入点。 它是衡量我们观察到的结果必须多么极端才能拒绝实验的原假设的一种度量。 alpha 的最常用值为 0.05 或 0.01。

通常,规则如下:

如果 p 值小于或等于 alpha(p < .05),则我们拒绝原假设,并声明结果具有统计学意义。

如果 p 值大于 alpha(p > .05),则我们无法拒绝原假设,并且我们说结果在统计上不显着。

在使用中看似随意的 alpha 值是常识性方法的缺点之一,与此方法有关的问题很多。 Nature 杂志上的文章重点介绍了一些问题。

有关此主题的更多详细信息,请参阅:

I 类和 II 类错误

错误有两种,如下所述:

  • I 类错误:在这种类型的错误中,当 H0 实际上为真时,我们拒绝 H0 。 陪审团的一个例子就是陪审团裁定无辜者犯有该人没有犯下的罪行。
  • II 类错误:在这种类型的错误中,当 H1 实际上为真时,我们无法拒绝 H0 。 这相当于有罪的人逃脱定罪。

统计假设检验

统计假设检验是一种使用统计研究或实验中的数据做出决策的方法。 在统计中,基于预定的阈值概率或显着性水平,如果不可能仅偶然发生的结果称为统计显着。 统计检验分为两类:单侧检验和双侧检验。

在双侧检验中,我们分配一半的 alpha 值来检验一个方向上的统计显着性,而另一半则分配给另一方向上的统计显着性。

在单侧检验中,检验仅在一个方向上执行。

有关此主题的更多详细信息,请参考这里

背景

要应用统计推断,重要的是要了解所谓的抽样分布的概念。 假设我们从原假设成立的总体中随机抽样,则抽样分布是统计量所有可能值及其概率的集合。

更为简单的定义是:抽样分布是统计数据可以假设(分布)的一组值,如果我们要从总体中重复抽取样本及其相关概率。

统计量的值是来自统计量抽样分布的随机样本。 通过获取许多不同大小的样本并取其平均值来计算平均值的采样分布。 它的平均值 Background 等于 Background,标准差 Background Background 等于 Background Background

CLT 指出,如果原始或原始评分总体呈正态分布,或者样本量足够大,则抽样分布呈正态分布。 传统上,统计人员将足够大的样本数量表示为 Background,即样本数量为 30 或更大。 但是,这仍然是一个争论的话题。

有关此主题的更多详细信息,请参阅这里这里

采样分布的标准差通常称为平均值的标准误差或仅称为标准误差。

Z 检验

z 检验适用于以下情况:

  • 该研究仅涉及一个样本均值,并且原假设人口的参数 The z-testThe z-test 是已知的
  • 均值的采样分布为正态分布
  • 样本大小为 The z-test

当总体平均值为已知时,我们使用 Z 检验。 在 Z 检验中,我们问一个问题:总体平均值 The z-test 是否不同于假设值。 Z 检验的原假设如下:

The z-test

其中 The z-test 人口平均值

The z-test =假设值

替代假设 The z-test 可以是以下之一:

The z-test

The z-test

The z-test

前两个是单侧检验,最后一个是双侧检验。 具体来说,为了检验 The z-test,我们计算了检验统计量:

The z-test

在此,The z-testThe z-test 的采样分布的真实标准差。 如果 The z-testtrue,则 Z 检验统计量将具有标准正态分布。

在这里,我们给出 Z 检验的快速说明。

假设我们有一家虚构的公司 Intelligenza,该公司声称他们提出了一种根本的新方法来改善内存保留和学习。 他们声称他们的技术可以提高成绩,而不是传统的学习技术。 假设使用传统的学习技术,成绩提高 40% ,标准差提高 10%。

使用 Intelligenza 方法对 100 名学生进行了随机检验,结果平均提高了 44%。 Intelligenza 的主张是否成立?

本研究的原假设指出,与传统的学习技术相比,使用 Intelligenza 的方法在成绩上没有任何改善。 另一种假设是,使用 Intelligenza 的方法比传统的研究技术有所改进。

原假设由以下给出:

The z-test

备用假设由以下给出:

The z-test

std = 10 / sqrt(100) = 1

z = (43.75-40) / (10/10) = 3.75 std

回想一下,如果原假设为真,则检验统计量 z 将具有如下所示的标准正态分布:

The z-test

作为参考,请访问这里

z的值是来自标准正态分布的随机样本,如果原假设为真,则为 z 的分布。

z = 43.75的观测值对应于标准正态分布曲线上的极端离群 p 值,远小于 0.1%。

p 值是曲线下的面积,在前一条正态分布曲线上的 3.75 值的右侧。

这表明,如果我们从标准正态分布中进行抽样,那么我们极不可能获得检验统计量的观察值。

我们可以使用scipy.stats包,使用 Python 查找实际的 p 值,如下所示:

In [104]: 1 - stats.norm.cdf(3.75)
Out[104]: 8.841728520081471e-05

因此,The z-test,即,如果检验统计量呈正态分布,则获得观测值的概率为 8.8e-05,接近零。 因此,如果原假设是真实的,几乎不可能获得我们观察到的值。

用更正式的术语来说,我们通常将定义一个阈值或 alpha 值,如果p ≤ α则拒绝原假设,否则将拒绝。

alpha 的典型值为 0.05 或 0.01。 以下列表说明了 alpha 的不同值:

  • p < 0.01 :反对 H0 的非常有力的证据
  • 0.01 < p < 0.05 :反对 H0 的有力证据
  • 0.05 < p < 0.1 :反对 H0 的证据不充分
  • p > 0.1 :几乎没有反对 H0 的证据

因此,在这种情况下,我们将拒绝原假设,并相信 Intelligenza 的主张,并声明其主张具有重大意义。 在这种情况下,反对原假设的证据很重要。 我们使用两种方法来确定是否拒绝原假设:

  • P 值法
  • 拒绝区域法

我们在前面的示例中使用的方法是后者。

p 值越小,原假设为真的可能性就越小。 在拒绝区域方法中,我们具有以下规则:

如果 The z-test,则拒绝原假设,否则保留原假设。

T 检验

当总体的标准差已知时,Z 检验最有用。 但是,在大多数实际情况下,这是一个未知数。 对于这些情况,我们转向显着性的 T 检验。

对于 T 检验,假设总体的标准差未知,我们将其替换为样本的标准差s。 平均值的标准误差现在变为:

The t-test

样本s的标准差计算如下:

The t-test

分母是N-1而不是N。 该值称为自由度数。 我现在将不加说明地指出,通过 CLT,随着N的出现,T 分布近似于正态,瓜斯或 Z 分布,因此N-1增大,即随着自由度df)。 当df = ∞时,T 分布与正态分布或 Z 分布相同。 这是直观的,因为随着df的增加,样本量也会增加,并且 s 接近 The t-test,即总体的真实标准差。 存在无限数量的 T 分布,每个 T 分布对应于不同的df值。

如下图所示:

The t-test

该图像的引用来自这里

有关 T 分布,Z 分布和自由度之间关系的更详细的技术说明,请参见这里

T 检验的类型

有各种类型的 T 检验。 以下是最常见的; 他们通常会制定一个原假设,以对分布的均值提出要求:

  • 单样本独立 T 检验:用于将样本的平均值与已知总体平均值或已知值的平均值进行比较。 假设我们是澳大利亚的健康研究人员,他们关注土著人口的健康,并希望确定低收入土著母亲所生的婴儿出生体重是否低于正常体重。

    单样本 T 检验的原假设检验的一个例子是:从低收入土著母亲的 150 例足月活产婴儿分娩中,我们样本的平均出生体重,与澳大利亚一般人口的婴儿平均出生体重无异,即 3367 克。

    该信息的参考是这里

  • 独立样本 T 检验:用于将独立样本的均值进行比较。 独立样本 T 检验的一个示例是自动变速器与手动变速器车辆的燃油经济性比较。 这是我们实际示例中要重点关注的内容。

    T 检验的原假设是:手动和自动变速箱汽车的平均燃油效率在城市/高速公路平均行驶里程之间没有差异。

  • 配对样本 T 检验:在成对/相关样本 T 检验中,我们采用有意义的方式将一个样本中的每个数据点与另一个样本中的数据点配对。 一种方法是在不同的时间点对同一样本进行测量。 这样的一个例子是通过比较饮食前后受试者的体重来检查减肥饮食的功效。

    在这种情况下,原假设是这样的:减肥饮食前后参与者的平均体重之间没有差异,或更简洁地说,配对观察之间的平均差异为零。

    可以在这里上找到此信息的参考。

T 检验示例

简而言之,要进行空有效假设检验NHST),我们需要执行以下操作:

  1. 制定我们的原假设。 原假设是我们的系统模型,假设我们希望验证的效果实际上是偶然的。
  2. 计算我们的 p 值。
  3. 将计算出的 p 值与我们的 alpha 值或阈值进行比较,并决定是否拒绝或接受原假设。 如果 p 值足够低(低于 alpha),我们将得出结论,即原假设可能是不正确的。

对于我们的实际插图,我们希望调查手动变速箱车辆是否比自动变速箱更省油。 为此,我们将利用美国政府 2014 年发布的燃料经济数据

In [53]: import pandas as pd
 import numpy as np
 feRawData = pd.read_csv('2014_FEGuide.csv')

In [54]: feRawData.columns[:20]
Out[54]: Index([u'Model Year', u'Mfr Name', u'Division', u'Carline', u'Verify Mfr Cd', u'Index (Model Type Index)', u'Eng Displ', u'# Cyl', u'Trans as listed in FE Guide (derived from col AA thru AF)', u'City FE (Guide) - Conventional Fuel', u'Hwy FE (Guide) - Conventional Fuel', u'Comb FE (Guide) - Conventional Fuel', u'City Unadj FE - Conventional Fuel', u'Hwy Unadj FE - Conventional Fuel', u'Comb Unadj FE - Conventional Fuel', u'City Unrd Adj FE - Conventional Fuel', u'Hwy Unrd Adj FE - Conventional Fuel', u'Comb Unrd Adj FE - Conventional Fuel', u'Guzzler? ', u'Air Aspir Method'], dtype='object')

In [51]: feRawData = feRawData.rename(columns={'Trans as listed in FE Guide (derived from col AA thru AF)' :'TransmissionType',
 'Comb FE (Guide) - Conventional Fuel' : 'CombinedFuelEcon'})

In [57]: transType=feRawData['TransmissionType']
 transType.head()
Out[57]: 0      Auto(AM7)
 1     Manual(M6)
 2      Auto(AM7)
 3     Manual(M6)
 4    Auto(AM-S7)
 Name: TransmissionType, dtype: object

现在,我们希望修改前面的序列,以使这些值仅包含AutoManual字符串。 我们可以这样做,如下所示:

In [58]: transTypeSeries = transType.str.split('(').str.get(0)
 transTypeSeries.head()
Out[58]: 0      Auto
 1    Manual
 2      Auto
 3    Manual
 4      Auto
 Name: TransmissionType, dtype: object

现在,我们从序列中创建一个最终的修改后的数据帧,该数据帧由变速箱类型和组合的燃油经济性数字组成:

In [61]: feData=pd.DataFrame([transTypeSeries,feRawData['CombinedFuelEcon']]).T
 feData.head()
Out[61]:    TransmissionType    CombinedFuelEcon
 0  Auto                16
 1  Manual              15
 2  Auto                16
 3  Manual              15
 4  Auto                17
 5 rows × 2 columns

现在,我们可以将自动变速箱的数据与手动变速箱的数据分开,如下所示:

In [62]: feData_auto=feData[feData['TransmissionType']=='Auto']
 feData_manual=feData[feData['TransmissionType']=='Manual']
In [63]: feData_auto.head()
Out[63]:   TransmissionType     CombinedFuelEcon
 0  Auto                 16
 2  Auto                 16
 4  Auto                 17
 6  Auto                 16
 8  Auto                 17
 5 rows × 2 columns

这表明有 987 辆具有自动变速箱的车辆,而有 211 辆具有手动变速箱的车辆:

In [64]: len(feData_auto)
Out[64]: 987

In [65]: len(feData_manual)
Out[65]: 211

In [87]: np.mean(feData_auto['CombinedFuelEcon'])
Out[87]: 22.173252279635257

In [88]: np.mean(feData_manual['CombinedFuelEcon'])
Out[88]: 25.061611374407583

In [84]: import scipy.stats as stats
 stats.ttest_ind(feData_auto['CombinedFuelEcon'].tolist(), 
 feData_manual['CombinedFuelEcon'].tolist())
Out[84]: (array(-6.5520663209014325), 8.4124843426100211e-11)

In [86]: stats.ttest_ind(feData_auto['CombinedFuelEcon'].tolist(), 
 feData_manual['CombinedFuelEcon'].tolist(), 
 equal_var=False)
Out[86]: (array(-6.949372262516113), 1.9954143680382091e-11)

置信区间

在本节中,我们将讨论置信区间的问题。 置信区间使我们能够对总体给定样本数据的平均值进行概率估计。

此估计称为间隔估计,由一些值(间隔)组成,这些值充当未知总体参数的良好估计。

置信区间受置信限度限制。 95% 置信区间定义为一个区间,其中该区间包含概率为 95% 的总体平均值。 那么,我们如何构建置信区间?

假设我们有一个双侧 T 检验,我们希望构建一个 95% 的置信区间。 在这种情况下,我们希望与平均值对应的样本 t 值 Confidence intervals 满足以下不等式:

Confidence intervals

给定 Confidence intervals,我们可以用前面的不等式关系来代替它:

Confidence intervals

Confidence intervals 区间是我们的 95% 置信区间。

概括而言,任何百分比 y 的任何置信区间都可以表示为 Confidence intervals,其中 Confidence intervalsConfidence intervals 的 t 尾值,即 Confidence intervals 对应于 y 的期望置信区间。

现在,我们将借此机会说明如何使用流行的统计环境 R 中的数据集来计算置信区间。stats模型的模块提供了对 R 核心数据包中可通过[ get_rdataset函数。

一个说明性示例

我们将考虑称为faithful的数据集,该数据集包含通过观察美国黄石国家公园的老忠实间歇泉的爆发而获得的数据。数据集中的两个变量是爆发,即间歇泉爆发的时间长度,然后是等待直到下一次爆发的时间间隔。 有 272 个观察结果。

In [46]: import statsmodels.api as sma
 faithful=sma.datasets.get_rdataset("faithful")
 faithful
Out[46]: <class 'statsmodels.datasets.utils.Dataset'>

In [48]: faithfulDf=faithful.data
 faithfulDf.head()
Out[48]:    eruptions   waiting
 0   3.600       79
 1   1.800       54
 2   3.333       74
 3   2.283       62
 4  4.533        85
5 rows × 2 columns

In [50]: len(faithfulDf)
Out[50]: 272

让我们计算间歇喷泉平均等待时间的 95% 置信区间。 为此,我们首先获取数据的样本均值和标准差:

In [80]: mean,std=(np.mean(faithfulDf['waiting']),
 np.std(faithfulDf['waiting']))

现在,我们使用scipy.stats包来计算置信区间:

In [81]: from scipy import statsN=len(faithfulDf['waiting'])
 ci=stats.norm.interval(0.95,loc=mean,scale=std/np.sqrt(N))
In [82]: ci
Out[82]: (69.28440107709261, 72.509716569966201)

因此,我们可以说[69.28,72.51]区间包含了间歇泉的实际平均等待时间,具有 95% 的置信度。

此信息的参考:这里这里

相关和线性回归

确定两个变量之间关系的统计中最常见的任务之一是它们之间是否存在依赖关系。 相关性是我们在统计数据中用于表示相互依赖的变量的总称。

然后,我们可以使用这种关系来尝试从另一组变量预测一组变量的值; 这称为回归。

相关性

用相关关系表示的统计依存关系并不意味着两个变量之间存在因果关系。 与此相关的著名观点是“相关并不意味着因果关系”。 因此,两个变量或数据集之间的相关性仅表示是偶然的,而不是因果关系或依赖性。 例如,在特定日期购买的冰淇淋数量与天气之间存在相关性。

有关相关性和依赖性的更多信息,请参见这里

相关度量(称为相关系数)是一个数字,用于捕获两个变量之间关系的大小和方向。 方向的范围从 -1 到 +1,幅度的范围从 0 到 1。 关系的方向通过符号表示,其中+符号表示正相关,而-符号表示负相关。 幅度越高,与被称为完美相关的相关性就越大。

最受欢迎和使用最广泛的相关系数是皮尔逊积矩相关系数,称为 r 。 它测量两个 x 和 y 变量之间的线性相关性或依赖性,并获取 -1+1 之间的值。

样本相关系数 r 定义如下:

Correlation

也可以这样写:

Correlation

在此,我们省略了求和限制。

线性回归

如前所述,回归集中在使用两个变量之间的关系进行预测。 为了使用线性回归进行预测,必须计算最适合的直线。

如果所有点(变量的值)都位于一条直线上,则该关系被认为是完美的。 在实践中,这种情况很少发生,并且点并非都整齐地排列在一条直线上。 那么这种关系是不完美的。 在某些情况下,线性关系仅在对数转换的变量之间发生。 这是一个日志日志模型。 这种关系的一个例子是物理学中的幂定律分布,其中一个变量随另一变量的幂而变化。

因此,诸如 Linear regression 的表达式导致 Linear regression 线性关系。

有关更多信息,请参见这里

为了构建最适合的线,使用最小二乘法。 在这种方法中,最佳拟合线是在各点之间构造的最佳线,对于这些点,从每个点到该线的平方距离的总和最小。 这被认为是我们尝试使用线性回归建模的变量之间关系的最佳线性近似。 在这种情况下,最佳拟合线称为最小二乘回归线。

更正式地说,最小二乘回归线是对于从数据点到该线的垂直距离的平方和具有最小可能值的线。 这些垂直距离也称为残差。

因此,通过构造最小二乘回归线,我们试图最小化以下表达式:

Linear regression

一个说明性示例

现在,我们将通过一个示例来说明所有上述要点。 假设我们正在进行一项研究,其中我们想说明温度对发声频率的影响。 该示例的数据从 1948 年的《昆虫之歌》,乔治·皮尔斯中获得。乔治·皮尔斯(George Pierce)测量了在各种温度下昆虫发出的声波的频率。

我们希望调查的频率和温度,因为我们怀疑这是有关系的。 数据由 16 个数据点组成,我们将其读入数据帧:

In [38]: import pandas as pd
 import numpy as np
 chirpDf= pd.read_csv('cricket_chirp_temperature.csv')
In [39]: chirpDf
Out[39]:chirpFrequency  temperature
0       20.000000       88.599998
1       16.000000       71.599998
2       19.799999       93.300003
3       18.400000       84.300003
4       17.100000       80.599998
5       15.500000       75.199997
6       14.700000       69.699997
7       17.100000       82.000000
8       15.400000       69.400002
9       16.200001       83.300003
10      15.000000       79.599998
11      17.200001       82.599998
12      16.000000       80.599998
13      17.000000       83.500000
14      14.400000       76.300003
15 rows × 2 columns

首先,让我们绘制数据散点图以及回归线或最佳拟合线:

In [29]: plt.scatter(chirpDf.temperature,chirpDf.chirpFrequency,
 marker='o',edgecolor='b',facecolor='none',alpha=0.5)
 plt.xlabel('Temperature')
 plt.ylabel('Chirp Frequency')
 slope, intercept = np.polyfit(chirpDf.temperature,chirpDf.chirpFrequency,1)
 plt.plot(chirpDf.temperature,chirpDf.temperature*slope + intercept,'r')
 plt.show()

An illustrative example

从图中可以看出,温度和线性调频频率之间似乎存在线性关系。 现在,我们可以使用statsmodels.ols(普通最小二乘法)方法进一步进行调查:

[37]: chirpDf= pd.read_csv('cricket_chirp_temperature.csv')
 chirpDf=np.round(chirpDf,2)
 result=sm.ols('temperature ~ chirpFrequency',chirpDf).fit()
 result.summary()

Out[37]: OLS Regression Results
 Dep. Variable: temperature     R-squared:      0.697
 Model: OLS     Adj. R-squared: 0.674
 Method:        Least Squares   F-statistic:    29.97
 Date:  Wed, 27 Aug 2014     Prob (F-statistic):     0.000107
 Time:  23:28:14        Log-Likelihood: -40.348
 No. Observations:      15      AIC:    84.70
 Df Residuals:  13      BIC:    86.11
 Df Model:      1 
 coef     std err t     P>|t| [95.0% Conf. Int.]
 Intercept     25.2323 10.060  2.508 0.026 3.499 46.966
 chirpFrequency 3.2911  0.601  5.475 0.000 1.992 4.590

 Omnibus:        1.003   Durbin-Watson:  1.818
 Prob(Omnibus):  0.606   Jarque-Bera (JB):       0.874
 Skew:   -0.391  Prob(JB):       0.646
 Kurtosis:       2.114   Cond. No.       171.

除了R-squaredInterceptchirpFrequency值外,我们将忽略大多数上述结果。

从前面的结果可以得出,回归线的斜率为 3.29,温度轴上的截距为 25.23。 因此,回归线方程如下所示:temp = 25.23 + 3.29 * chirpFrequency

这意味着,随着线性调频频率增加一,温度会升高约 3.29 华氏度。 但是,请注意,截距值实际上并不有意义,因为它超出了数据的范围。 我们也只能预测数据范围内的值。 例如,我们无法预测 32 华氏度下的chirpFrequency是什么,因为它超出了数据的范围; 而且,在华氏 32 度时,昆虫会冻死。 相关系数 R 的值如下所示:

In [38]: R=np.sqrt(result.rsquared)
 R
Out[38]: 0.83514378678237422

因此,我们的相关系数为R = 0.835。 这表明温度的变化可以解释频率的约 84%。

该信息的参考:昆虫之歌

数据来自这里

有关单变量和多变量回归的更深入的处理,请访问以下网站:

总结

在本章中,我们简要介绍了统计的经典方法或常客方法,并向您展示了如何将 Pandas 与statsscipy.statsstatsmodels包结合在一起,以计算,解释和推断统计数据。

在下一章中,我们将研究另一种统计方法,即贝叶斯方法。 要更深入地了解我们涉及的统计信息主题,请查看了解行为科学中的统计信息

八、贝叶斯统计简介

在本章中,我们将简要介绍称为贝叶斯统计的另一种统计推断方法。 它不打算作为完整的入门手册,而只是对贝叶斯方法的介绍。 我们还将探讨相关的 Python 相关库,如何使用 Pandas 以及matplotlib来帮助进行数据分析。 将讨论的各种主题如下:

  • 贝叶斯统计概论
  • 贝叶斯统计的数学框架
  • 概率分布
  • 贝叶斯统计与频率统计
  • PyMC 和蒙特卡洛仿真简介
  • 贝叶斯推理的示例 – 切换点检测

贝叶斯统计概论

贝叶斯统计领域建立在 18 世纪统计学家,哲学家和长老会牧师托马斯·贝叶斯牧师的工作基础上。 他著名的贝叶斯定理(构成贝叶斯统计的理论基础)于 1763 年死后出版,以解决逆概率问题。 有关此主题的更多详细信息,请参考这里

逆概率问题在 18 世纪初期风行一时,通常表述如下:

假设您和朋友一起玩游戏。 袋子 1 中有 10 个绿色球和 7 个红色球,袋子 2 中有 4 个绿色和 7 个红色球。您的朋友背离您的视线,扔硬币并从其中一个袋子中随机捡起一个球,并显示给您。 球是红色的。 球从袋子 1 中抽出的概率是多少?

这些问题被称为逆概率问题,因为我们试图根据随后的事件(球是红色)来估计已经发生的事件(球从哪个袋子抽出)的概率。

Introduction to Bayesian statistics

贝叶斯球图

让我们快速说明一下如何解决前面说明的逆概率问题。 考虑到球是红色的,我们希望计算从袋子 1 中抽出球的概率。 这可以表示为 Introduction to Bayesian statistics

让我们从计算选择红球的概率开始。 如上图所示,可以通过遵循红色的两条路径来计算。 因此,我们有 Introduction to Bayesian statistics

现在,从袋子 1 中选择红色球的概率仅是通过上方路径,并给出如下:

Introduction to Bayesian statistics

并且,从袋 2 中选择红色球的概率如下所示:

Introduction to Bayesian statistics

注意,该概率可以写成如下形式:

Introduction to Bayesian statistics

通过检查我们可以看到 Introduction to Bayesian statistics 和树的最后一个分支只有在球首先位于袋 1 中并且是红色球时才被遍历。 因此,从直观上讲,我们将得到以下结果:

Introduction to Bayesian statistics

贝叶斯统计的数学框架

利用贝叶斯方法,我们提出了进行统计推断的另一种方法。 我们首先介绍贝叶斯定理,这是从其得出所有贝叶斯推断的基本方程式。

关于概率的几个定义是有序的:

  • Mathematical framework for Bayesian statistics:这些事件可能以一定概率发生。
  • Mathematical framework for Bayesian statisticsMathematical framework for Bayesian statistics:这是发生特定事件的概率。
  • Mathematical framework for Bayesian statistics:鉴于 B 发生,这是 A 发生的概率。 这称为条件概率。
  • Mathematical framework for Bayesian statistics:这是 A 和 B 一起出现的概率。

我们从基本假设开始,如下所示:

Mathematical framework for Bayesian statistics

前面的方程将联合概率P(AB)与条件概率P(A | B)和边际概率P(B)关联起来。 如果重写方程式,则条件概率表达式如下:

Mathematical framework for Bayesian statistics

这在某种程度上很直观-给定 B 的概率是通过将 A 和 B 都出现的概率除以 B 发生的概率而获得的。 这个想法是给定的,所以我们除以它的概率。 可以在这个链接中找到对此方程式的更严格的处理,标题为概率:联合概率,边际概率和条件概率

类似地,通过对称我们有 Mathematical framework for Bayesian statistics。 因此,我们有 Mathematical framework for Bayesian statistics。 通过在两侧将表达式除以 Mathematical framework for Bayesian statistics 并假设P(B) != 0,我们得到:

Mathematical framework for Bayesian statistics

前面的等式称为贝叶斯定理,这是所有贝叶斯统计推断的基础。 为了将贝叶斯定理与推论统计联系起来,我们将等式重铸为所谓的历时解释,如下所示:

Mathematical framework for Bayesian statistics

其中,Mathematical framework for Bayesian statistics 代表一个假设。

Mathematical framework for Bayesian statistics 代表已经发生的事件,我们将其用于统计研究中,也称为数据

然后,Mathematical framework for Bayesian statistics 是我们的假设在观察数据之前的概率。 这称为先验概率。 贝叶斯统计学家经常认为使用先验概率是一种优势,因为先验知识或先前的结果可以用作当前模型的输入,从而提高了准确性。 有关此的更多信息,请参考这里

Mathematical framework for Bayesian statistics 是获得与假设无关的观测数据的概率。 这称为归一化常数。 并非总是需要计算归一化常数,尤其是在许多流行的算法(例如 MCMC)中,我们将在本章后面进行讨论。

给定我们观察到的数据,Mathematical framework for Bayesian statistics 是假设成立的概率。 这称为后验。

考虑到我们的假设,Mathematical framework for Bayesian statistics 是获得数据的概率。 这称为可能性。

因此,贝叶斯统计等于应用贝叶斯规则来解决推论统计中的问题,其中 H 代表我们的假设,D 代表数据。

贝叶斯统计模型是根据参数进行转换的,这些参数的不确定性由概率分布表示。 这与将值视为确定性的频率论方法不同。 替代表示如下:

Mathematical framework for Bayesian statistics

其中 Mathematical framework for Bayesian statistics 是我们的未知数据,Mathematical framework for Bayesian statistics 是我们的观测数据

在贝叶斯统计中,我们对先验数据进行假设,并使用贝叶斯规则使用可能性来更新后验概率。 作为说明,让我们考虑以下问题。 这是通常称为问题的经典案例:

  • 两个包含彩球
  • 一个包含 50 个红色和 50 个蓝色球
  • n 个包含 30 个红色和 70 个蓝色球
  • 随机选择两个中的一个(概率为 50%),然后从两个中的一个随机抽出一个球

如果画了一个红色的球,那么它来自的概率是多少? 我们想要 Mathematical framework for Bayesian statisticsMathematical framework for Bayesian statistics

在这里,Mathematical framework for Bayesian statistics 表示从 Ur 中抽出球,Mathematical framework for Bayesian statistics 表示被抽出的球是红色:

Mathematical framework for Bayesian statistics

我们知道 Mathematical framework for Bayesian statisticsMathematical framework for Bayesian statisticsMathematical framework for Bayesian statisticsMathematical framework for Bayesian statistics

因此,我们得出结论 Mathematical framework for Bayesian statistics

贝叶斯理论与赔率

贝叶斯定理有时可以通过使用称为赔率的概率的替代表示形式,以更自然,更方便的形式表示。 赔率通常用比率表示,并且经常使用。 一匹马在比赛中获胜的赔率为 3 比 1(通常为 3:1),这表示该马有望以 75% 的概率获胜。

给定概率 p,可以将几率计算为几率= Bayes theory and odds,在 p = 0.75 的情况下,该几率变为 0.75:0.25,即 3:1。

我们可以通过使用赔率来重写贝叶斯定理的形式:

Bayes theory and odds

贝叶斯统计的应用

贝叶斯统计可以应用于经典统计中遇到的许多问题,例如:

  • 参数估计
  • 预测
  • 假设检验
  • 线性回归

研究贝叶斯统计数据有许多令人信服的理由。 其中一些是利用先验信息更好地告知当前模型。 贝叶斯方法适用于概率分布而不是点估计,因此产生了更现实的预测。 贝叶斯推断是基于可用数据的假设 -- P(假设|数据)。 惯常方法试图根据假设拟合数据。 可以说,贝叶斯方法是更逻辑和经验的方法,因为它试图将信念建立在事实之上,而不是反过来。 有关此的更多信息,请参考这里

概率分布

在本节中,我们将简要检查各种概率分布的属性。 这些分布中有许多用于贝叶斯分析。 因此,需要简要的提要。 我们还将说明如何使用matplotlib生成和显示这些分布。 为了避免在每个部分的每个代码段重复导入语句,我将介绍以下标准的 Python 代码导入集,这些代码需要在以下命令中提到的任何代码段之前运行。 您只需在每个会话中运行一次这些导入。 导入如下:

In [1]: import pandas as pd
 import numpy as np
 from matplotlib import pyplot as plt
 from matplotlib import colors
 import matplotlib.pyplot as plt
 import matplotlib
 %matplotlib inline

拟合分布

贝叶斯分析中必须采取的步骤之一是使我们的数据适合概率分布。 选择正确的分发可能有点技巧,并且通常需要统计知识和经验,但是我们可以遵循一些准则来帮助我们。 这些如下:

  • 确定数据是离散数据还是连续数据
  • 检查数据的偏斜度/对称性,如果偏斜,请确定方向
  • 确定下限和上限(如果有)
  • 确定观察分布中极值的可能性

统计试验是具有一组明确定义的结果(称为样本空间)的可重复实验。 伯努利试验是“是/否”实验,如果是,则将随机 X 变量的值分配为 1,如果否,则将其分配为 0。抛硬币并查看其是否朝正面方向的事件是伯努利审判的例子。

有两类概率分布:离散分布和连续分布。 在以下各节中,我们将讨论这两类分布之间的差异,并浏览主要分布。

离散概率分布

在这种情况下,变量只能采用某些不同的值,例如整数。 离散随机变量的一个例子是当我们掷硬币 5 次时获得的正面数。 可能的值为{0,1,2,3,4,5}。 例如,我们无法获得 3.82 个平面。 随机变量可以采用的值的范围由概率质量函数pmf)指定。

离散均匀分布

离散均匀分布是一种对事件进行建模的分布,该事件具有一组有限的可能结果,其中每个结果均可能被观察到。 对于 Discrete uniform distributions 结果,每个结果都有 Discrete uniform distributions 发生的可能性。

这方面的一个例子就是投掷均匀的色子。 六个结果中任何一个的概率为 Discrete uniform distributions。 PMF 由 Discrete uniform distributions 给出,期望值和方差分别由 Discrete uniform distributionsDiscrete uniform distributions 给出。

In [13]: from matplotlib import pyplot as plt
 import matplotlib.pyplot as plt
 X=range(0,11)
 Y=[1/6.0 if x in range(1,7) else 0.0 for x in X]
 plt.plot(X,Y,'go-', linewidth=0, drawstyle='steps-pre', 
 label="p(x)=1/6")
 plt.legend(loc="upper left")
 plt.vlines(range(1,7),0,max(Y), linestyle='-')
 plt.xlabel('x')
 plt.ylabel('p(x)')
 plt.ylim(0,0.5)
 plt.xlim(0,10)
 plt.title('Discrete uniform probability distribution with 
 p=1/6')
 plt.show()

Discrete uniform distributions

离散均匀分布

伯努利分布

伯努利分布用于衡量试验成功的可能性; 例如,硬币抛起头或尾的概率。 这可以用一个随机的 X 变量来表示,如果硬币正面朝上,则取值为 1,反之则为 0。 出现头部或尾部的概率分别由pq = 1-p表示。

可以通过以下 pmf 表示:

The Bernoulli distribution

期望值和方差由以下公式给出:

The Bernoulli distribution

此信息的参考位于这里

现在,我们使用matplotlibscipy.stats绘制伯努利分布,如下所示:

In [20]:import matplotlib
 from scipy.stats import bernoulli
 a = np.arange(2)

 colors = matplotlib.rcParams['axes.color_cycle']
 plt.figure(figsize=(12,8))
 for i, p in enumerate([0.0, 0.2, 0.5, 0.75, 1.0]):
 ax = plt.subplot(1, 5, i+1)
 plt.bar(a, bernoulli.pmf(a, p), label=p, color=colors[i], alpha=0.5)
 ax.xaxis.set_ticks(a)

 plt.legend(loc=0)
 if i == 0:
 plt.ylabel("PDF at $k$")

 plt.suptitle("Bernoulli probability for various values of $p$")
Out[20]:

The Bernoulli distribution

二项分布

二项分布用于表示 n 个独立伯努利试验即 The binomial distribution 中的成功次数。

以掷硬币为例,此分布模拟了 The binomial distribution 试验获得 X 正面攻击的机会。 对于 100 次抛掷,二项分布模拟了 0 头(极不可能)至 50 头(最高可能性)至 100 头(也极不可能)的可能性。 当赔率完全均匀时,这最终使二项分布对称,而当赔率远不那么均匀时,则使二项分布偏斜。 pmf 由以下表达式给出:

The binomial distribution

期望和方差分别由以下表达式给出:

The binomial distribution

In [5]: from scipy.stats import binom
 clrs = ['blue','green','red','cyan','magenta']     plt.figure(figsize=(12,6))
 k = np.arange(0, 22)
 for p, color in zip([0.001, 0.1, 0.3, 0.6, 0.999], clrs):
 rv = binom(20, p)
 plt.plot(k, rv.pmf(k), lw=2, color=color, label="$p$=" + str(round(p,1)))

 plt.legend()
 plt.title("Binomial distribution PMF")
 plt.tight_layout()
 plt.ylabel("PDF at $k$")
 plt.xlabel("$k$")
Out[5]:

The binomial distribution

二项分布

泊松分布

泊松分布对给定时间间隔内多个事件的概率进行建模,假设这些事件以已知的平均速率发生,并且连续事件的发生与自上一个事件以来的时间无关。

可以由泊松分布模型化的过程的一个具体示例是,如果一个人平均每天收到 23 封电子邮件。 如果我们假设电子邮件的到达时间是相互独立的,则个人每天收到的电子邮件总数可以通过泊松分布来建模。

另一个示例可能是每小时在特定车站停靠的火车数量。 泊松分布的 pmf 由以下表达式给出:

The Poisson distribution

其中 The Poisson distribution 是速率参数,代表每单位时间发生的预期事件/到达次数,The Poisson distribution 是随机变量,代表事件/到达次数。

期望值和方差分别由以下公式给出:

The Poisson distribution

The Poisson distribution

有关更多信息,请参考这里

使用matplotlib将 pmf 绘制为各种值,如下所示:

In [11]: %matplotlib inline
 import numpy as np
 import matplotlib
 import matplotlib.pyplot as plt
 from scipy.stats import poisson
 colors = matplotlib.rcParams['axes.color_cycle']
 k=np.arange(15)
 plt.figure(figsize=(12,8))
 for i, lambda_ in enumerate([1,2,4,6]):
 plt.plot(k, poisson.pmf(k, lambda_), '-o', 
 label="$\lambda$=" + str(lambda_), color=colors[i])
 plt.legend()
 plt.title("Possion distribution PMF for various $\lambda$")
 plt.ylabel("PMF at $k$")
 plt.xlabel("$k$")
 plt.show()
Out[11]:

The Poisson distribution

鱼类分布

几何分布

对于独立的伯努利试验,几何分布测量获得成功所需的试验次数 X。 它还可以表示首次成功之前的失败次数 The Geometric distribution

pmf 由以下表达式给出:

The Geometric distribution

The Geometric distribution 以来,上述表达式是有意义的,并且如果需要 k 次尝试才能获得一个成功(p),则意味着我们必须有等于 The Geometric distributionThe Geometric distribution 失败。

期望值和方差如下:

The Geometric distribution

以下命令清楚地解释了前面的公式:

In [12]: from scipy.stats import geom
 p_vals=[0.01,0.2,0.5,0.8,0.9]
 x = np.arange(geom.ppf(0.01,p),geom.ppf(0.99,p))
 colors = matplotlib.rcParams['axes.color_cycle']
 for p,color in zip(p_vals,colors):
 x = np.arange(geom.ppf(0.01,p),geom.ppf(0.99,p))
 plt.plot(x,geom.pmf(x,p),'-o',ms=8,label='$p$=' + str(p))
 plt.legend(loc='best')
 plt.ylim(-0.5,1.5)
 plt.xlim(0,7.5)
 plt.ylabel("Pmf at $k$")
 plt.xlabel("$k$")
 plt.title("Geometric distribution PMF")
Out[12]:

The Geometric distribution

几何分布

负二项分布

同样对于独立的伯努利试验,负二项分布测量在指定的成功次数 r 发生之前的尝试次数 The negative binomial distribution。 一个例子是要获得 5 个硬币的掷硬币次数。 pmf 给出如下:

The negative binomial distribution

期望和方差分别由以下表达式给出:

The negative binomial distribution

我们可以看到负二项是几何分布的概括,几何分布是负二项的特殊情况,其中 The negative binomial distribution

代码和图解如下所示:

In [189]: from scipy.stats import nbinom
 from matplotlib import colors
 clrs = matplotlib.rcParams['axes.color_cycle']
 x = np.arange(0,11)
 n_vals = [0.1,1,3,6]
 p=0.5
 for n, clr in zip(n_vals, clrs):
 rv = nbinom(n,p)
 plt.plot(x,rv.pmf(x), label="$n$=" + str(n), color=clr)
 plt.legend()
 plt.title("Negative Binomial Distribution PMF")
 plt.ylabel("PMF at $x$")
 plt.xlabel("$x$")

The negative binomial distribution

连续概率分布

在连续概率分布中,变量可以采用任何实数。 与离散概率分布一样,它不限于一组有限的值。 例如,一个健康的新生婴儿的平均体重大约在 6-9 磅之间。 例如,其重量可以是 7.3 磅。 连续概率分布的特征在于概率密度函数PDF)。

随机变量可以假定的所有概率之和为 1。因此,概率密度函数图的面积为 1。

连续均匀分布

均匀分布对随机变量 X 建模,该随机变量 X 可以以相等的概率采用 The continuous uniform distribution 范围内的任何值。

PDF 由 The continuous uniform distribution 给出,否则由 The continuous uniform distributionThe continuous uniform distribution 给出。

期望和方差由以下表达式给出:

The continuous uniform distribution

The continuous uniform distribution

在下面的代码和图中,生成了连续均匀的概率分布,并针对各种样本大小绘制了该图:

In [11]: np.random.seed(100)  # seed the random number generator
 # so plots are reproducible
 subplots = [111,211,311]
 ctr = 0 
 fig, ax = plt.subplots(len(subplots), figsize=(10,12))
 nsteps=10
 for i in range(0,3):
 cud = np.random.uniform(0,1,nsteps) # generate distrib
 count, bins, ignored = ax[ctr].hist(cud,15,normed=True)
 ax[ctr].plot(bins,np.ones_like(bins),linewidth=2, color='r')
 ax[ctr].set_title('sample size=%s' % nsteps)
 ctr += 1
 nsteps *= 100
 fig.subplots_adjust(hspace=0.4)
 plt.suptitle("Continuous Uniform probability distributions for various sample sizes" , fontsize=14)

The continuous uniform distribution

指数分布

指数分布模拟了泊松过程中两个事件之间的等待时间。 泊松过程是遵循泊松分布的过程,在该过程中,事件以已知的平均速率不可预测地发生。 指数分布可以描述为几何分布的连续极限,并且也是马尔科夫的(无记忆)。

无记忆的随机变量表现出这样的特性,即其未来状态仅取决于有关当前时间的相关信息,而不取决于过去的信息。 建模马尔科夫/无记忆随机变量的示例是对短期股票价格行为及其遵循随机游走的思想进行建模。 这导致了所谓的金融有效市场假说。 有关更多信息,请参见这里

指数分布的 PDF 由 The exponential distribution = The exponential distribution 给出。 期望和方差由以下表达式给出:

The exponential distribution

有关参考,请参考这个链接

分布图和代码如下:

In [15]: import scipy.stats
 clrs = colors.cnames
 x = np.linspace(0,4, 100)
 expo = scipy.stats.expon
 lambda_ = [0.5, 1, 2, 5]
 plt.figure(figsize=(12,4))
 for l,c in zip(lambda_,clrs):
 plt.plot(x, expo.pdf(x, scale=1./l), lw=2,
 color=c, label = "$\lambda = %.1f$"%l)
 plt.legend()
 plt.ylabel("PDF at $x$")
 plt.xlabel("$x$")
 plt.title("Pdf of an Exponential random variable for various $\lambda$");

The exponential distribution

正态分布

统计中最重要的分布可以说是正态/高斯分布。 它对中心值周围的概率分布进行建模,而没有左右偏倚。 遵循正态分布的现象有很多示例,例如:

  • 婴儿的出生体重
  • 测量误差
  • 血压
  • 考试分数

正态分布的重要性由中心极限定理强调,该极限定理指出,独立于同一分布而绘制的许多随机变量的均值近似正态,而与原始分布的形式无关。 其期望值和方差如下:

The normal distribution

正态分布的 PDF 由以下表达式给出:

The normal distribution

以下代码和图解说明了公式:

In [54]: import matplotlib
 from scipy.stats import norm
 X = 2.5
 dx = 0.1
 R = np.arange(-X,X+dx,dx)

 L = list()
 sdL = (0.5,1,2,3)
 for sd in sdL:
 f = norm.pdf
 L.append([f(x,loc=0,scale=sd) for x in R])

 colors = matplotlib.rcParams['axes.color_cycle']
 for sd,c,P in zip(sdL,colors,L):
 plt.plot(R,P,zorder=1,lw=1.5,color=c,
 label="$\sigma$=" + str(sd))
 plt.legend()
 ax = plt.axes()
 ax.set_xlim(-2.1,2.1)
 ax.set_ylim(0,1.0)
 plt.title("Normal distribution Pdf")
 plt.ylabel("PDF at $\mu$=0, $\sigma$")

The normal distribution

可以在这个位置找到有关绘制分布图的 Python 代码的参考。

正态分布也可以视为二项分布的连续极限,而其他分布则可以视为 The normal distribution。 我们可以在命令中看到二项分布,并绘制如下:

In [18]:from scipy.stats import binom
 from matplotlib import colors
 cols = colors.cnames
 n_values = [1, 5,10, 30, 100]

 subplots = [111+100*x for x in range(0,len(n_values))]
 ctr = 0 
 fig, ax = plt.subplots(len(subplots), figsize=(6,12))
 k = np.arange(0, 200)
 p=0.5

 for n, color in zip(n_values, cols):
 k=np.arange(0,n+1)
 rv = binom(n, p)
 ax[ctr].plot(k, rv.pmf(k), lw=2, color=color)
 ax[ctr].set_title("$n$=" + str(n))
 ctr += 1

 fig.subplots_adjust(hspace=0.5)
 plt.suptitle("Binomial distribution PMF (p=0.5) for various values of n", fontsize=14)

The normal distribution

随着 n 的增加,二项分布接近正态分布。 实际上,对于n >= 30,可以在前面的图中清楚地看到。

贝叶斯统计与频率统计

在当今的统计中,关于如何解释数据和进行统计推断有两种思路。 迄今为止,经典且占主导地位的方法是所谓的“惯常方法”(请参阅​​第 7 章,“统计学之旅 -– 经典方法”),本章中的贝叶斯方法。

概率是多少?

贝叶斯和频繁主义世界观之间的辩论的核心是问题—我们如何定义概率?

在频率论世界观中,概率是从重复事件的发生频率得出的概念。 例如,当我们定义抛掷均匀硬币时获胜的概率等于一半。 这是因为当我们反复抛掷一个均匀的硬币时,当抛硬币的数量足够大时,正面数除以抛硬币的总数将接近 0.5。

贝叶斯世界观是不同的,概率的概念是它与一个人对事件发生的信念程度有关。 因此,对于贝叶斯统计学家而言,认为均匀色子概率为 5 的可能性为 What is probability?,这与我们对该事件发生的可能性的信念有关。

如何定义模型

从模型定义的角度来看,常客通过使用重复的实验来分析数据和计算的度量标准如何变化,同时保持模型参数不变。 另一方面,贝叶斯算法利用固定的实验数据,但是改变了他们对模型参数的置信度,其解释如下:

  • 频率论:如果模型固定,则数据会有所不同
  • 贝叶斯:如果数据固定,则模型会有所不同

惯常方法使用所谓的最大似然方法来估计模型参数。 它涉及从一组独立且分布均匀的观测值生成数据,并将观测到的数据拟合到模型中。 最适合该数据的模型参数的值是最大似然估计器MLE),有时可能是所观察数据的函数。

贝叶斯主义对这个问题的看法不同于概率框架。 概率分布用于描述值的不确定性。 贝叶斯实践者使用观察到的数据估计概率。 为了计算这些概率,它们使用单​​个估计量,即贝叶斯公式。 与“惯常”方法一样,这产生的是分布而不是点估计。

置信(频率)与可信(贝叶斯)区间

让我们比较一下 95% 的置信区间的含义,贝叶斯(Bayesian)练习者使用了 95% 可信区间的常用术语,可信区间为 95% 。

在频率论框架中,置信区间为 95% 意味着如果您无限次重复实验,并在过程中生成间隔,则其中 95% 的间隔将包含我们尝试估计的参数,通常称为 θ。 在这种情况下,时间间隔是随机变量,而不是参数估计值θ,该值在频率论世界观中是固定的。

在贝叶斯可信区间的情况下,我们的解释与归因于频繁置信区间的常规解释更加一致。 因此,我们有 Confidence (Frequentist) versus Credible (Bayesian) intervals。 在这种情况下,我们可以正确地得出结论:θ在此区间内的可能性为 95% 。

有关更多信息,请参阅频率论和贝叶斯主义:有什么大不了的?| SciPy 2014,Jake VanderPlas

进行贝叶斯统计分析

进行贝叶斯统计分析涉及以下步骤:

  1. 指定概率模型:在这一步中,我们使用概率分布充分描述模型。 根据我们采集的样本的分布,我们尝试为其拟合模型,并尝试将概率分配给未知参数。
  2. 计算后验分布:后验分布是我们根据观察到的数据计算出的分布。 在这种情况下,我们将直接应用贝叶斯公式。 将根据上一步中指定的概率模型来指定它。
  3. 检查我们的模型:这是我们进行推断之前检查模型及其输出的必要步骤。 贝叶斯推断方法使用概率分布将概率分配给可能的结果。

似然函数和 PyMC 的蒙特卡洛估计

贝叶斯统计不仅是另一种方法。 它是实践统计的完全替代范例。 给定我们收集的数据,它使用概率模型进行推断。 这可以用基本表达式表示为P(H | D)

在这里,H 是我们的假设,即我们要证明的事物,D 是我们的数据或观察值。

在前面的讨论中提醒我们,贝叶斯定理的历时形式如下:

Monte Carlo estimation of the likelihood function and PyMC

在这里,P(H)是无条件的先验概率,表示我们进行试验之前所知道的。 假设我们的假设是正确的,P(D | H)是我们的似然函数或获得我们观察到的数据的概率。

P(D)是数据的概率,也称为归一化常数。 这可以通过对 H 上的分子进行积分来获得。

似然函数是贝叶斯计算中最重要的部分,它封装了有关数据中未知数的所有信息。 它与逆概率质量函数有些相似。

反对采用贝叶斯方法的一个论点是,先验的计算可能是主观的。 有很多观点支持这种方法。 其中之一是,如先前所述,可以包括外部先验信息。

似然值表示未知积分,在简单情况下可以通过解析积分获得。

对于涉及高维积分的更复杂的用例,需要蒙特卡洛MC)积分,并且可以用于计算似然函数。

MC 集成可以通过多种采样方法来计算,例如统一采样,分层采样和重要性采样。 在蒙特卡洛积分中,我们可以将积分近似如下:

Monte Carlo estimation of the likelihood function and PyMC

我们可以通过以下有限和来近似积分:

Monte Carlo estimation of the likelihood function and PyMC

其中,x 是来自 g 的样本向量。 可以根据大量定律并通过确保模拟误差较小来获得这一估计的好证据。

在 Python 中进行贝叶斯分析时,我们需要一个模块,该模块使我们能够使用前面所述的蒙特卡洛方法来计算似然函数。 PyMC库满足了这一需求。 它提供了通常称为马尔可夫链蒙特卡洛MCMC)的蒙特卡洛方法。 我不会进一步研究 MCMC 的技术细节,但是有兴趣的读者可以在以下参考文献的PyMC中找到有关 MCMC 实现的更多信息:

MCMC 不是通用的灵丹妙药; 该方法存在一些缺点,其中之一是算法收敛缓慢。

贝叶斯分析示例 -– 切换点检测

在这里,我们将尝试使用贝叶斯推理并为一个有趣的数据集建模。 所涉及的数据集包含作者的 FacebookFB)过去的历史记录。 我们已经清理了 FB 历史数据并将日期保存在fb_post_dates.txt文件中。 文件中的数据如下所示:

head -2 ../fb_post_dates.txt 
Tuesday, September 30, 2014 | 2:43am EDT
Tuesday, September 30, 2014 | 2:22am EDT

因此,我们看到一个日期时间序列,代表作者在 FB 上发布的日期和时间。 首先,我们将文件读入数据帧,将时间戳分为“日期”和“时间”列:

In [91]: filePath="./data/fb_post_dates.txt"
 fbdata_df=pd.read_csv(filePath,  sep='|', parse_dates=[0], header=None,names=['Date','Time'])

接下来,我们检查数据如下:

In [92]: fbdata_df.head()  #inspect the data
Out[92]:   Date       Time
0  2014-09-30   2:43am EDT
1  2014-09-30   2:22am EDT
2  2014-09-30   2:06am EDT
3  2014-09-30   1:07am EDT
4  2014-09-28   9:16pm EDT

现在,我们按日期对数据编制索引,创建一个DatetimeIndex,以便可以对其进行重采样以按月进行计数,如下所示:

In [115]: fbdata_df_ind=fbdata_df.set_index('Date')
 fbdata_df_ind.head(5)
Out[115]:                      Time
 Date
 2014-09-30  2:43am EDT
 2014-09-30  2:22am EDT
 2014-09-30  2:06am EDT
 2014-09-30  1:07am EDT
 2014-09-28  9:16pm EDT

我们显示有关索引的信息,如下所示:

In [116]: fbdata_df_ind.index
Out[116]: <class 'pandas.tseries.index.DatetimeIndex'>
 [2014-09-30, ..., 2007-04-16]
 Length: 7713, Freq: None, Timezone: None

现在,我们使用重新采样按月获取帖子数:

In [99]: fb_mth_count_=fbdata_df_ind.resample('M', how='count')
 fb_mth_count_.rename(columns={'Time':'Count'},
 inplace=True)   # Rename 
 fb_mth_count_.head()
Out[99]:            Count
 Date
 2007-04-30  1
 2007-05-31  0
 2007-06-30  5
 2007-07-31  50
 2007-08-31  24 

日期格式显示为每月的最后一天。 现在,我们创建一个 2007-2015 年 FB 帖子计数的散点图,并使点的大小与matplotlib中的值成比例:

In [108]: %matplotlib inline
 import datetime as dt
#Obtain the count data from the DataFrame as a dictionary
 year_month_count=fb_bymth_count.to_dict()['Count'] 
 size=len(year_month_count.keys())
#get dates as list of strings
 xdates=[dt.datetime.strptime(str(yyyymm),'%Y%m') 
 for yyyymm in year_month_count.keys()] 
 counts=year_month_count.values()
 plt.scatter(xdates,counts,s=counts)
 plt.xlabel('Year')
 plt.ylabel('Number of Facebook posts')
 plt.show() 

Bayesian analysis example – Switchpoint detection

我们要调查的问题是,在一段时间内,行为是否有所改变。 具体来说,我们希望确定是否在特定时期内更改了 FB 职位的平均人数。 在时间序列中,这通常称为切换点或更改点。

我们可以利用泊松分布对此建模。 您可能还记得,泊松分布可用于对时间序列计数数据进行建模。 (有关更多信息,请参考这里。)

如果我们用 Bayesian analysis example – Switchpoint detection 表示我们每月的 FB 职位数,则可以如下表示我们的模型:

Bayesian analysis example – Switchpoint detection

Bayesian analysis example – Switchpoint detection 参数是泊松分布的rate参数,但我们不知道它的值是多少。 如果我们查看 FB 时间序列计数数据的散点图,我们可以看到在 2010 年中后期左右某个时候的帖子数量有所增加,这可能与 2010 年南非世界杯的开始恰好相吻合。 作者参加了。

Bayesian analysis example – Switchpoint detection 参数是切换点,即速率参数更改时的时间,而 Bayesian analysis example – Switchpoint detectionBayesian analysis example – Switchpoint detection 分别是切换点之前和之后的 Bayesian analysis example – Switchpoint detection 参数的值。 可以表示如下:

Bayesian analysis example – Switchpoint detection

注意,上面 Bayesian analysis example – Switchpoint detection 中指定的变量都是贝叶斯随机变量。 对于代表人们对其值的信念的贝叶斯随机变量,我们需要使用概率分布对它们进行建模。 我们想推断 Bayesian analysis example – Switchpoint detectionBayesian analysis example – Switchpoint detection 的值,这些值是未知的。 在PyMC中,我们可以使用随机和确定性类表示随机变量。 我们注意到,指数分布是泊松事件之间的时间量。 因此,在 Bayesian analysis example – Switchpoint detectionBayesian analysis example – Switchpoint detection 的情况下,我们选择指数分布来对其建模,因为它们可以是任何正数:

Bayesian analysis example – Switchpoint detection

Bayesian analysis example – Switchpoint detection

Bayesian analysis example – Switchpoint detection 的情况下,我们将选择使用均匀分布对其进行建模,这反映了我们的信念,即切换点很有可能在整个时间段的任何一天发生。 因此,我们有:

Bayesian analysis example – Switchpoint detection

在此,Bayesian analysis example – Switchpoint detectionBayesian analysis example – Switchpoint detection 对应于年份 Bayesian analysis example – Switchpoint detection 的上下边界。 现在让我们使用PyMC表示我们先前开发的模型。 现在,我们将使用PyMC来查看是否可以在 FB 发布数据中检测到切换点。 除了散点图,我们还可以在条形图中显示数据。 为了做到这一点,首先,我们需要获取列表中按月排序的 FB 帖子数:

In [69]: fb_activity_data = [year_month_count[k] for k in 
 sorted(year_month_count.keys())]
 fb_activity_data[:5]

Out[70]: [1, 0, 5, 50, 24]

In [71]: fb_post_count=len(fb_activity_data)

我们使用matplotlib渲染条形图:

In [72]: from IPython.core.pylabtools import figsize
 import matplotlib.pyplot as plt
 figsize(8, 5)
 plt.bar(np.arange(fb_post_count),
 fb_activity_data, color="#49a178")
 plt.xlabel("Time (months)")
 plt.ylabel("Number of FB posts")
 plt.title("Monthly Facebook posts over time")
 plt.xlim(0,fb_post_count);

Bayesian analysis example – Switchpoint detection

查看前面的条形图,可以得出结论,FB 频率发布行为是否随时间变化? 我们可以在开发的模型上使用PyMC来帮助我们找出更改,如下所示:

In [88]: # Define data and stochastics
 import pymc as pm
 switchpoint = pm.DiscreteUniform('switchpoint',
 lower=0,
 upper=len(fb_activity_data)-1,
 doc='Switchpoint[month]')
 avg = np.mean(fb_activity_data)
 early_mean = pm.Exponential('early_mean', beta=1./avg)
 late_mean = pm.Exponential('late_mean', beta=1./avg)
 late_mean
Out[88]:<pymc.distributions.Exponential 'late_mean' at 0x10ee56d50>

在这里,我们为速率参数r定义了一种方法,并使用泊松分布对计数数据进行建模,如前所述:

In [89]: @pm.deterministic(plot=False)
 def rate(s=switchpoint, e=early_mean, l=late_mean):
 ''' Concatenate Poisson means '''
 out = np.zeros(len(fb_activity_data))
 out[:s] = e
 out[s:] = l
 return out

 fb_activity = pm.Poisson('fb_activity', mu=rate, 
 value=fb_activity_data, observed=True)
 fb_activity
Out[89]: <pymc.distributions.Poisson 'fb_activity' at 0x10ed1ee50>

在前面的代码片段中,@pm.deterministic是一个修饰符,它表示速率函数是确定性的,这意味着其值完全由其他变量(在这种情况下为esl)确定。 为了告诉PyMC将比率函数转换为确定性对象,装饰器是必需的。 如果不指定装饰器,则会发生错误。 (有关更多信息,请参阅这里,以获取有关 Python 装饰器的信息。)

有关更多信息,请参阅以下网页:

现在,我们使用 FB Count 数据(fb_activity)和 Bayesian analysis example – Switchpoint detection(分别为early_meanlate_meanrate)参数创建一个模型。

接下来,使用Pymc创建一个 MCMC 对象,该对象使我们能够使用马尔科夫链蒙特卡洛方法拟合数据。 然后,我们在所得的 MCMC 对象上调用样本以进行拟合:

In [94]: fb_activity_model=pm.Model([fb_activity,early_mean,
 late_mean,rate])
In [95]: from pymc import MCMC
 fbM=MCMC(fb_activity_model)
In [96]: fbM.sample(iter=40000,burn=1000, thin=20)
 [-----------------100%-----------------] 40000 of 40000 
 complete in 11.0 sec

使用 MCMC 拟合模型涉及使用马尔科夫链蒙特卡洛方法生成后验P(s, e, l | D)的概率分布。 它使用蒙特卡洛过程反复模拟数据采样,并执行此操作,直到基于多个条件该算法似乎收敛到稳态为止。 这是一个马尔可夫过程,因为连续的样本仅取决于先前的样本。 (有关马尔可夫链收敛的更多参考,请参考这里。)

生成的样本称为迹线。 我们可以通过查看迹线的直方图来查看参数的边缘后验分布:

In [97]: from pylab import hist,show
 %matplotlib inline
 hist(fbM.trace('late_mean')[:])
Out[97]: (array([  15.,   61.,  214.,  421.,  517.,  426.,  202.,
 70.,   21.,    3.]),
 array([ 102.29451192,  103.25158404,  104.20865616,
 105.16572829,  106.12280041,  107.07987253,
 108.03694465,  108.99401677,  109.95108889,
 110.90816101,  111.86523313]),
 <a list of 10 Patch objects>)

Bayesian analysis example – Switchpoint detection

In [98]:plt.hist(fbM.trace('early_mean')[:])
Out[98]: (array([  20.,  105.,  330.,  489.,  470.,  314.,  147.,
 60.,    3.,   12.]),
 array([ 49.19781192,  50.07760882,  50.95740571,
 51.83720261,  52.71699951,  53.59679641,
 54.47659331,  55.35639021,  56.2361871 ,
 57.115984  ,  57.9957809 ]),
 <a list of 10 Patch objects>)

Bayesian analysis example – Switchpoint detection

在这里,我们可以看到Switchpoint的月数形式:

In [99]: fbM.trace('switchpoint')[:]
Out[99]: array([38, 38, 38, ..., 35, 35, 35])

In [150]: plt.hist(fbM.trace('switchpoint')[:])
Out[150]: (array([ 1899.,     0.,     0.,     0.,     0.,     0.,
 0., 0., 0.,    51.]),
 array([ 35\. ,  35.3,  35.6,  35.9,  36.2,  36.5,  36.8,
 37.1,  37.4, 37.7,  38\. ]),
 <a list of 10 Patch objects>)

Bayesian analysis example – Switchpoint detection

历史切换点

我们可以看到Switchpoint在第 35-38 个月左右。 在这里,我们使用matplotlib在单个图中显示esl的边际后验分布:

In [141]: early_mean_samples=fbM.trace('early_mean')[:]
 late_mean_samples=fbM.trace('late_mean')[:]
 switchpoint_samples=fbM.trace('switchpoint')[:]
In [142]: from IPython.core.pylabtools import figsize
 figsize(12.5, 10)
 # histogram of the samples:
 fig = plt.figure()
 fig.subplots_adjust(bottom=-0.05)

 n_mths=len(fb_activity_data)
 ax = plt.subplot(311)
 ax.set_autoscaley_on(False)

 plt.hist(early_mean_samples, histtype='stepfilled',
 bins=30, alpha=0.85, label="posterior of $e$",
 color="turquoise", normed=True)
 plt.legend(loc="upper left")
 plt.title(r"""Posterior distributions of the variables
 $e, l, s$""",fontsize=16)
 plt.xlim([40, 120])
 plt.ylim([0, 0.6])
 plt.xlabel("$e$ value",fontsize=14)

 ax = plt.subplot(312)
 ax.set_autoscaley_on(False)
 plt.hist(late_mean_samples, histtype='stepfilled',
 bins=30, alpha=0.85, label="posterior of $l$",
 color="purple", normed=True)
 plt.legend(loc="upper left")
 plt.xlim([40, 120])
 plt.ylim([0, 0.6])
 plt.xlabel("$l$ value",fontsize=14)
 plt.subplot(313)
 w = 1.0 / switchpoint_samples.shape[0] *
 np.ones_like(switchpoint_samples)
 plt.hist(switchpoint_samples, bins=range(0,n_mths), alpha=1,
 label=r"posterior of $s$", color="green",
 weights=w, rwidth=2.)
 plt.xlim([20, n_mths - 20])
 plt.xlabel(r"$s$ (in days)",fontsize=14)
 plt.ylabel("probability")
 plt.legend(loc="upper left")

 plt.show()

Bayesian analysis example – Switchpoint detection

边缘后验分布

PyMC 还具有绘图功能。 (它使用matplotlib。)在下面的图中,我们显示了时间序列图,自相关图(acorr)以及为早期均值,晚期均值和切换点绘制的样本的直方图。 直方图可用于可视化后验分布。 自相关图显示上一周期的值是否与当前周期的值密切相关。

In [100]: from pymc.Matplot import plot
 plot(fbM)
 Plotting late_mean
 Plotting switchpoint
 Plotting early_mean

以下是最新的均值图:

Bayesian analysis example – Switchpoint detection

pymc_comprehensive_late_mean

在这里,我们显示切换点图:

Bayesian analysis example – Switchpoint detection

Pymc 综合切换点

在这里,我们显示早期均值图:

Bayesian analysis example – Switchpoint detection

pymc 综合早期均值

从 PyMC 的输出中,我们可以得出结论,转换点距离时间序列开始大约 35-38 个月。 这对应于 2010 年 3 月至 7 月左右的某个时间。作者可以证明这是他使用 FB 的标志性一年,因为那是在南非举行的足球(足球)世界杯决赛的年份, 他参加了。

参考资料

要更深入地了解我们涉及的贝叶斯统计主题,请查看以下参考资料:

总结

在本章中,我们对过去几年中统计和数据分析中最热门的趋势之一-贝叶斯统计推断方法进行了一次旋风式浏览。 我们在这里覆盖了很多领域。

我们研究了贝叶斯统计方法所需要的内容,并讨论了为什么贝叶斯观点是一种引人注目的观点的各种因素-事实胜于信念。 我们解释了关键的统计分布,并展示了如何使用各种统计包在matplotlib中生成和绘制它们。

我们解决了一个相当困难的话题,但又没有过多简化,并演示了如何使用 PyMC 包和蒙特卡洛模拟方法来展示贝叶斯统计数据的能力,以建立模型,进行趋势分析以及对真实数据集进行推断(Facebook 用户帖子)。 在下一章中,我们将讨论 Pandas 库的架构。

九、Pandas 库架构

在本章中,我们研究了可供 Pandas 用户使用的各种库。 本章旨在作为简短指南,帮助用户围绕 Pandas 提供的各种模块和库进行导航和查找。 它提供了有关库代码组织方式的分解,还提供了有关各个模块的简要说明。 对于感兴趣的用户来说,这对他们下面的 Pandas 的内部工作以及希望为代码库做出贡献的用户来说,将是最有价值的。 我们还将简要演示如何使用 Python 扩展来提高性能。 将讨论的各种主题如下:

  • Pandas 库层次结构简介
  • Pandas 模块和文件的描述
  • 使用 Python 扩展来提高性能

Pandas 文件层次结构简介

通常,在安装时,会将 Pandas 作为 Python 模块安装在第三方 Python 模块的标准位置:

平台 标准安装位置 示例
Unix/MacOS prefix/lib/pythonX.Y/site-packages /usr/local/lib/Python 2.7/site-packages
Windows prefix\Lib\site-packages C:\Python27\Lib\site-packages

安装的文件遵循特定的层次结构:

  • pandas/core:此文件包含用于基本数据结构的文件,例如序列/数据帧和相关函数。
  • pandas/src:包含用于实现基本算法的 Cython 和 C 代码。
  • pandas/io:它包含输入/输出工具(例如平面文件,Excel,HDF5,SQL 等)。
  • pandas/tools:它包含辅助数据算法合并和连接例程,连接,数据透视表等。
  • pandas/sparse:它包含序列,数据帧,面板等的稀疏版本。
  • pandas/stats:包含线性回归和面板回归,以及移动窗口回归。 这应该由 statsmodels 中的功能代替。
  • pandas/util:包含工具,开发和测试工具。
  • pandas/rpy:包含用于连接到 R 的 RPy2 接口。

注意

作为参考,请参见这里

Pandas 模块和文件的描述

在本节中,我们将简要介绍组成 Pandas 库的各个子模块和文件。

Pandas /core

该模块包含 Pandas 的核心子模块。 讨论如下:

  • api.py:这将导入一些关键模块供以后使用。

  • array.py:这可以隔离 Pandas 对 numPy 的暴露,即所有直接的 numPy 使用。

  • base.py:这定义了基本类别,例如StringMixinPandasObject,它们是各种 Pandas 对象(例如PeriodPandasSQLTablesparse.array.SparseArray/SparseListinternals.Blockinternals.BlockManagergeneric.NDFramegroupby.GroupBybase.FrozenListbase.FrozenNDArrayio.sql.PandasSQLio.sql.PandasSQLTabletseries.period.PeriodFrozenListFrozenNDArrayIndexOpsMixinDatetimeIndexOpsMixin

  • common.py:这定义了用于处理数据结构的通用工具方法。 例如,isnull对象检测到缺少的值。

  • config.py:这是用于处理包范围内的可配置对象的模块。 它定义了以下类别:OptionErrorDictWrapperCallableDynamicDocoption_contextconfig_init

  • datetools.py:这是处理 Python 中日期的函数的集合。

  • frame.py:这定义了 Pandas 的数据帧类及其各种方法。数据帧继承自NDFrame。 (见下文)。

  • generic.py:这定义了通用NDFrame基类,它是 Pandas 的数据帧,序列和面板类的基类。 NDFrame源自base.py中定义的 Pandas 对象。 NDFrame可以看作是 Pandas 数据帧的 N 维版本。 有关此的更多信息,请访问这里

  • categorical.py:这定义了分类,这是一个从 Pandas 对象派生的类,它表示分类变量 la R/S-plus。 (我们稍后会扩展您的知识)。

  • format.py:这定义了整个格式化程序类,例如CategoricalFormatterSeriesFormatterTableFormatterDataFrameFormatterHTMLFormatterCSVFormatterExcelCellExcelFormatterGenericArrayFormatterFloatArrayFormatterIntArrayFormatterDatetime64FormatterTimedelta64FormatterEngFormatter

  • groupby.py:这定义了启用groupby函数的各种类。 讨论如下:

    • Splitter classes:这包括DataSplitterArraySplitterSeriesSplitterFrameSplitterNDFrameSplitter
    • Grouper/Grouping classes:这包括GrouperGroupByBaseGrouperBinGrouperGroupingSeriesGroupByNDFrameGroupBy
  • ops.py: 这定义了一个内部 API,用于对 Pandas 对象进行算术运算。 它定义了向对象添加算术方法的函数。 它定义了一个_create_methods元方法,该方法用于使用算术,比较和布尔方法构造器创建其他方法。 add_methods方法采用一些新方法,将它们添加到现有方法列表中,并将其绑定到其相应的类。 add_special_arithmetic_methodsadd_flex_arithmetic_methods方法调用_create_methodsadd_methods将算术方法添加到类中。

    它还定义了_TimeOp类,该类是与日期时间相关的算术运算的包装。 它包含Wrapper函数,用于对序列,数据帧和面板函数进行算术,比较和布尔运算-_arith_method_SERIES(..)_comp_method_SERIES(..)_bool_method_SERIES(..)_flex_method_SERIES(..)_arith_method_FRAME(..)_comp_method_FRAME(..)_flex_comp_method_FRAME(..)_arith_method_PANEL(..)_comp_method_PANEL(..)

  • index.py:这定义了Index类及其相关函数。 所有 Pandas 的对象(序列,数据帧和面板)都使用索引来存储轴标签。 它的下面是一个不可变的数组,提供了可以切片的有序集合。

  • internals.py:这定义了多个对象类。 这些列出如下:

    • Block:这是具有 Pandas 其他功能的同质 N 维numpy.ndarray对象。 例如,它使用__slots__将对象的属性限制为ndimvalues_mgr_locs。 它充当其他Block子类的基类。
    • NumericBlock:这是数字类型的Blocks的基类。
    • FloatOrComplexBlock:这是从NumericBlock继承的FloatBlockComplexBlock的基类
    • ComplexBlock:这是处理具有复杂类型的Block对象的类。
    • FloatBlock:这是处理浮点型Block对象的类。
    • IntBlock:这是处理具有整数类型的Block对象的类。
    • TimeDeltaBlockBoolBlockDatetimeBlock:这些是timedeltaBooleandatetimeBlock类。
    • ObjectBlock:这是为用户定义的对象处理Block对象的类。
    • SparseBlock:这是处理相同类型的稀疏数组的类。
    • BlockManager:这是管理一组Block对象的类。 它不是公共 API 类。
    • SingleBlockManager:这是管理一个Block的类。
    • JoinUnit:这是Block对象的工具类。
  • matrix.py:这将数据帧导入为DataMatrix

  • nanops.py:这些是用于处理 NaN 值的类和函数。

  • ops.py:这定义了 Pandas 对象的算术运算。 它不是公共 API。

  • panel.pypanel4d.pypanelnd.py:这些提供了 Pandas 的面板对象的功能。

  • series.py:它定义序列从NDFrameIndexOpsMixin继承的 pandas Series类及其各种方法。

  • sparse.py:这定义用于处理稀疏数据结构的导入。 稀疏数据结构被压缩,从而省略了与 NaN 匹配或缺少值的数据点。 有关此的更多信息,请访问这里

  • strings.py: 它们具有处理字符串的各种函数。

    pandas/core

Pandas /io

该模块包含用于数据 I/O 的各种模块。 讨论如下:

  • api.py:这定义了数据 I/O API 的各种导入。
  • auth.py:这定义了处理身份验证的各种方法。
  • common.py:这定义了 I/O API 的通用函数。
  • data.py:这定义用于处理数据的类和方法。 DataReader方法从各种在线来源(例如 Yahoo 和 Google)读取数据。
  • date_converters.py:定义日期转换函数。
  • excel.py:此模块解析和转换 Excel 数据。 这定义了ExcelFileExcelWriter类。
  • ga.py:这是 Google Analytics(分析)功能的模块。
  • gbq.py:这是 Google BigQuery 的模块。
  • html.py:这是用于处理 HTML I/O 的模块。
  • json.py:这是用于处理 Pandas 中 json I/O 的模块。 这定义了WriterSeriesWriterFrameWriterParserSeriesParserFrameParser类。
  • packer.py:这是 msgpack 序列化程序支持,用于将 Pandas 数据结构读取和写入磁盘。
  • parsers.py:此模块定义了各种函数和类,这些函数和类用于解析和处理文件以创建 Pandas 的数据帧。 下文讨论的所有三个read_*函数都具有多个可配置的读取选项。 有关更多详细信息,请参见此参考
    • read_csv(..):这定义了pandas.read_csv()函数,可用于将 CSV 文件的内容读取到数据帧中。
    • read_table(..):这会将制表符分隔的表文件读取到数据帧中。
    • read_fwf(..):这会将固定宽度格式的文件读取到数据帧中。
    • TextFileReader:这是用于读取文本文件的类。
    • ParserBase:这是解析器对象的基类。
    • CParserWrapperPythonParser:这些分别是 C 和 Python 的解析器。 它们都继承自ParserBase
    • FixedWidthReader:这是读取固定宽度数据的类。 定宽数据文件包含文件中特定位置的字段。
    • FixedWithFieldParser:这是用于解析从PythonParser继承的固定宽度字段的类。
  • pickle.py:这提供了腌制(序列化)Pandas 对象的方法。 讨论如下:
    • to_pickle(..):这会将对象序列化为文件。
    • read_pickle(..):这将从文件中读取序列化的对象到 pandas 对象。 仅应与受信任的来源一起使用。
  • pytables.py:这是PyTables模块的接口,用于将 Pandas 数据结构读取和写入磁盘上的文件。
  • sql.py:它是类和函数的集合,用于使能够从试图与数据库无关的关系数据库中检索数据。 讨论如下:
    • PandasSQL:这是将 Pandas 与 SQL 连接的基类。 它提供了必须由子类实现的伪read_sqlto_sql方法。
    • PandasSQLAlchemy:这是PandasSQL的子类,它可以使用SQLAlchemy在数据帧和 SQL 数据库之间进行转换。
    • PandasSQLTable类:它将 Pandas 表(数据帧)映射到 SQL 表。
    • pandasSQL_builder(..):这将根据提供的参数返回正确的PandasSQL子类。
    • PandasSQLTableLegacy类:这是PandasSQLTable的旧支持版本。
    • PandasSQLLegacy类:这是PandasSQLTable的旧支持版本。
    • get_schema(..):这将获取给定框架的 SQL 数据库表架构。
    • read_sql_table(..):这将 SQL DB 表读入数据帧。
    • read_sql_query(..):这会将 SQL 查询读取到数据帧中。
    • read_sql(..):这将 SQL 查询/表读入数据帧。
    • to_sql(..):此操作将数据帧中存储的记录写入 SQL 数据库。
  • stata.py:此工具包含用于将Stata文件处理为 Pandas 数据帧的工具。
  • wb.py:这是用于从世界银行网站下载数据的模块。

Pandas tools

  • util.py:具有定义的其他util函数,例如match(..)cartesian_product(..)compose(..)

  • tile.py:具有一组函数,这些函数可以量化输入数据,从而实现tile函数。 除了cut(..)qcut(..)之外,大多数函数都是内部函数。

  • rplot.py:这是一个模块,提供在 Pandas 中生成网格图的函数。

  • plotting.py: 这提供了一组以序列或数据帧为参数的绘图函数。

    • scatter_matrix(..):这将绘制散点图矩阵

    • andrews_curves(..):此图将多元数据绘制为曲线,这些曲线是使用样本作为傅立叶级数的系数创建的

    • parallel_coordinates(..):这是一种绘图技术,可让您查看数据中的聚类并直观地估计统计信息

    • lag_plot(..):用于检查数据集或时间序列是否随机

    • autocorrelation_plot(..):用于检查时间序列中的随机性

    • bootstrap_plot(..):此图用于以视觉方式确定统计量度的不确定性,例如平均值或中位数

    • radviz(..): 该图用于可视化多元数据

      提示

      以上信息的参考来自这里

  • pivot.py: 此函数用于处理 Pandas 中的数据透视表。 它是主要函数pandas.tools.pivot_table(..),它创建类似于电子表格的数据透视表作为数据帧

    提示

    以上信息的参考来自这里

  • merge.py:提供合并序列,数据帧和面板对象(例如merge(..)concat(..))的函数

  • describe.py:这提供了一个value_range(..)函数,该函数以序列的形式返回数据帧的最大值和最小值。

Pandas /sparse

这是提供序列,数据帧和面板的稀疏实现的模块。 所谓稀疏,是指省略或丢失诸如 0 之类的值的数组。

有关此的更多信息,请访问这里

  • api.py:这是一组便利导入
  • array.py:它是SparseArray数据结构的实现
  • frame.py:它是SparseDataFrame数据结构的实现
  • list.py:它是SparseList数据结构的实现
  • panel.py:它是SparsePanel数据结构的实现
  • series.py:它是SparseSeries数据结构的实现

Pandas stats

  • api.py:这是一组便利导入。
  • common.py:定义模块中其他函数调用的内部函数。
  • fama_macbeth.py:包含 Fama-Macbeth 回归的类定义和函数。 有关 FM 回归的更多信息,请访问这里
  • interface.py:它定义ols(..),它返回普通最小二乘OLS)回归对象。 它从pandas.stats.ols模块导入。
  • math.py:具有有用的函数,定义如下:
    • rank(..)solve(..)inv(..):它们分别用于矩阵秩,解和逆
    • is_psd(..):这检查矩阵的正定性
    • newey_west(..):用于协方差矩阵计算
    • calc_F(..):这将计算 F 统计信息
  • misc.py:用于其他函数。
  • moments.py:这提供了滚动和扩展的统计量度,包括在 Cython 中实现的时刻。 这些方法包括:rolling_count(..)rolling_cov(..)rolling_corr(..)rolling_corr_pairwise(..)rolling_quantile(..)rolling_apply(..)rolling_window(..)expanding_count(..)expanding_quantile(..)expanding_cov(..)expanding_corr(..)expanding_corr_pairwise(..)expanding_apply(..)ewma(..)ewmvar(..)ewmstd(..)ewmcov(..)ewmcorr(..)
  • ols.py:这实现 OLS 并提供 OLS 和MovingOLS类。 OLS 运行完整的样本最小二乘回归,而MovingOLS生成滚动或扩展的简单 OLS。
  • plm.py:这为面板数据提供了线性回归对象。 这些类的讨论如下:
    • PanelOLS:这是面板对象的 OLS
    • MovingPanelOLS:这是面板对象的滚动/扩展 OLS
    • NonPooledPanelOLS:-这是面板对象的非池化 OLS
  • var.py: 这提供了向量自回归类,讨论如下:
    • VAR:这是对序列和数据帧中的多元数据的向量自动回归

    • PanelVAR: 这是面板对象中多元数据的向量自动回归

      提示

      有关向量自回归的更多信息,请访问这里

Pandas /util

  • testing.py:这提供了断言,调试,单元测试以及其他用于测试的类/函数。 它包含许多特殊的断言函数,这些函数使检查序列,数据帧或面板对象是否等效的操作更加容易。 其中一些函数包括assert_equal(..)assert_series_equal(..)assert_frame_equal(..)assert_panelnd_equal(..)pandas.util.testing模块对 Pandas 代码库的贡献者特别有用。 它定义了一个util.TestCase类。 它还提供了用于处理语言环境,控制台调试,文件清除,比较器等的工具,以供潜在的代码库贡献者进行测试。

  • terminal.py:此函数主要是内部函数,与获取有关终端的某些特定详细信息有关。 唯一的暴露函数是get_terminal_size()

  • print_versions.py:它定义了get_sys_info()函数,该函数返回系统信息的字典,以及show_versions(..)函数,显示可用的 Python 库的版本。

  • misc.py:这定义了几个其他工具。

  • decorators.py: 这定义了一些装饰器函数和类。

    提示

    SubstitutionAppender类是装饰器,它们在函数docstrings上执行替换和附加操作,有关 Python 装饰器的更多信息,请访问这里

  • clipboard.py:这包含跨平台剪贴板方法,可通过键盘启用复制和粘贴功能。 Pandas I/O API 包括pandas.read_clipboard()pandas.to_clipboard(..)之类的函数。

Pandas /rpy

如果模块已安装在计算机中,则该模块将尝试提供 R 统计包的接口。 在版本 0.16.0 和更高版本中不推荐使用。 可以使用rpy2模块代替它的功能。

  • base.py:这为 R 中著名的lm函数定义了一个类
  • common.py:这提供了许多函数,可以将 Pandas 对象转换为等效的 R 版本
  • mass.py:这是rlm -- R 的lm函数的未实现版本
  • var.py:其中包含未实现的类VAR

Pandas /test

这个模块为 Pandas 中的各种物体提供了许多测试。 特定库文件的名称是不言自明的,除了邀请读者进行探索之外,在这里我将不进一步详细介绍。

Pandas /compat

与兼容性相关的功能解释如下:

  • chainmap.pychainmap_impl.py:这提供了一个ChainMap类,可以将多个dicts或映射分组,以便生成可以更新的单个视图
  • pickle_compat.py:此函数提供了在 0.12 之前的版本中腌制 Pandas 对象的功能。
  • openpyxl_compat.py:这检查openpyxl的兼容性

Pandas /calc

这是提供计算功能的模块,讨论如下:

  • api.py:这包含evalexpr的导入。
  • align.py:这实现了数据对齐的函数。
  • common.py:它包含几个内部函数。
  • engines.py:这定义了抽象引擎NumExprEnginePythonEnginePythonEngine求值表达式,主要用于测试目的。
  • eval.py:这定义了最重要的eval(..)函数以及其他一些重要函数。
  • expressions.py:通过numexpr提供快速的表达式求值。 numexpr函数用于加速某些数值运算。 它使用多个内核以及智能分块和缓存加速。 它定义了evaluate(..)where(..)方法。
  • ops.py:这定义了eval使用的运算符类。 它们是TermConstantOpBinOpDivUnaryOp
  • pytables.py:这为PyTables查询提供了查询接口。
  • scope.py:这是示波器操作的模块。 它定义了一个Scope类,它是一个保存范围的对象。

提示

有关numexpr的更多信息,请访问这里。 有关此模块用法的信息,请访问这里

Pandas /series

  • api.py:这是一组便捷导入

  • converter.py: 这定义了一组用于格式化和转换与日期时间相关的对象的类。 导入后,pandas 向matplotlib注册了一组单位转换器。

    • 这通过register()函数完成,说明如下:

      In [1]: import matplotlib.units as munits
      In [2]: munits.registry
      Out[2]: {}
      
      In [3]: import pandas
      In [4]: munits.registry
      Out[4]: 
      {pandas.tslib.Timestamp: <pandas.tseries.converter.DatetimeConverter instance at 0x7fbbc4db17e8>,
       pandas.tseries.period.Period: <pandas.tseries.converter.PeriodConverter instance at 0x7fbbc4dc25f0>,
       datetime.date: <pandas.tseries.converter.DatetimeConverter instance at 0x7fbbc4dc2fc8>,
       datetime.datetime: <pandas.tseries.converter.DatetimeConverter instance at 0x7fbbc4dc2a70>,
       datetime.time: <pandas.tseries.converter.TimeConverter instance at 0x7fbbc4d61e18>}
      
      
    • Converter:此类包括TimeConverterPeriodConverterDateTimeConverter

    • Formatters:此类包括TimeFormatterPandasAutoDateFormatterTimeSeries_DateFormatter

    • Locators:此类包括PandasAutoDateLocatorMilliSecondLocatorTimeSeries_DateLocator

    FormatterLocator类用于处理 matplotlib 绘图中的刻度。

  • frequencies.py:它定义用于指定时间序列对象的频率(每天,每周,每季度,每月,每年等)的代码。

  • holiday.py:这定义了用于处理假期的函数和类-HolidayAbstractHolidayCalendarUSFederalHolidayCalendar在定义的类中。

  • index.py:这定义了DateTimeIndex类。

  • interval.py:这定义了IntervalPeriodIntervalIntervalIndex类。

  • offsets.py:这定义了各种类别,包括处理与时间有关的时间段的偏移量。 这些解释如下:

    • DateOffset:这是提供日期时间段功能的类的接口,例如WeekWeekOfMonthLastWeekOfMonthQuarterOffsetYearOffsetEasterFY5253FY5253Quarter
    • BusinessMixin:这是业务对象的mixin类,用于提供与时间功能相关的类。 这将由BusinessDay类继承。 BusinessDay子类派生自BusinessMixinSingleConstructorOffset,并提供了营业日的补偿。
    • MonthOffset:这是提供月份时间段功能的类的接口,例如MonthEndMonthBeginBusinessMonthEndBusinessMonthBegin
    • MonthEndMonthBegin:这是一个月底或一个月初的日期偏移量。
    • BusinessMonthEndBusinessMonthBegin:这是一个工作日日历的结尾或开始一个月的日期偏移。
    • YearOffset:此偏移量是提供年份功能的类的接口,例如YearEndYearBeginBYearEndBYearBegin
    • YearEndYearBegin:这是一年末或年初的日期偏移量。
    • BYearEndBYearBegin:这是工作日日历结束或开始时一年的日期偏移。
    • Week:提供 1 周的补偿。
    • WeekDay:这提供了从工作日(Tue)到星期几(= 2)的映射。
    • WeekOfMonthLastWeekOfMonth:描述一个月中的一周中的日期
    • QuarterOffset:这是提供季度功能的类的接口,例如QuarterEndQuarterrBeginBQuarterEndBQuarterBegin
    • QuarterEndQuarterrBeginBQuarterEndBQuarterBegin:与Year*类相同,除了时间段是季度而不是年份。
    • FY5253FY5253Quarter:这些类别描述了 52-53 周的会计年度。 这也称为 4-4-5 日历。 您可以在这个链接中获得更多信息。
    • Easter:这是复活节假期的DateOffset
    • Tick:这是时间单位类的基本类,例如DayHourMinuteSecondMilliMicroNano
  • period.py:这为 PandasTimeSeries定义了PeriodPeriodIndex类。

  • plotting.py:这定义了各种绘图函数,例如tsplot(..),它绘制了一个序列。

  • resample.py:这定义了TimeGrouper,这是用于时间间隔分组的自定义groupby类。

  • timedeltas.py:这定义了to_timedelta(..)方法,该方法将其参数转换为timedelta对象。

  • tools.py:这定义了工具函数,例如to_datetime(..)parse_time_string(..)dateutil_parse(..)format(..)

  • util.py:这定义了更多工具函数,如下所示:

    • isleapyear(..):检查年份是否为闰年
    • pivot_annual(..):这按年份分组,表示闰年

Pandas /sandbox

该模块处理将 Pandas 数据帧集成到 PyQt 框架中。 有关 PyQt 的更多信息,请访问

使用 Python 扩展来提高性能

Python 和 pandas 用户的困扰之一是,语言和模块的易用性和表达性带来显着的缺点-性能-尤其是在数字计算方面。

根据编程基准站点,Python 通常比编译后的语言(例如,用于许多算法或数据结构操作的 C/C++)慢。 例如二进制树操作。 在以下参考中,Python3 的运行速度比 n 体仿真计算的最快 C++ 实现慢 104 倍。

那么,我们如何解决这个合法而令人烦恼的问题呢? 通过编写代码中对性能敏感的部分,我们可以减轻 Python 的这种速度,同时保持我们喜欢的性能(清晰度和生产率)。 例如数字处理,C/C++ 中的算法,并通过编写 Python 扩展模块由我们的 Python 代码调用它们

Python 扩展模块使我们能够从 Python 调用用户定义的 C/C++ 代码或库函数,从而使我们能够提高代码性能,但仍然受益于 Python 的易用性。

为了帮助我们了解什么是 Python 扩展模块,请考虑导入模块时 Python 中发生的情况。 导入语句导入一个模块,但这实际上是什么意思? 共有三种可能性,如下所示:

  • 某些 Python 扩展模块在构建时会链接到解释器。

  • 导入会导致 Python 将.pyc文件加载到内存中。 .pyc文件包含 Python 字节码。例如以下命令:

    In [3]: import pandas
     pandas.__file__
    Out[3]: '/usr/lib/Python 2.7/site-packages/pandas/__init__.pyc'
    
    
  • import语句导致将 Python 扩展模块加载到内存中。 .so(共享库)文件由机器代码组成。 例如,请参考以下命令:

    In [4]: import math
     math.__file__
    Out[4]: '/usr/lib/Python 2.7/lib-dynload/math.so'
    
    

我们将集中讨论第三种可能性。 即使我们正在处理从 C 编译的二进制共享对象,我们也可以将其作为 Python 模块导入,这显示了 Python 扩展的强大功能-应用可以从 Python 机器代码或机器代码导入模块,并且接口相同 。 Cython 和 SWIG 是使用 C 和 C++ 编写扩展的两种最受欢迎​​的方法。 在编写扩展时,我们包装了 C/C++ 机器代码,并将其转换为行为像纯 Python 代码的 Python 扩展模块。 在这个简短的讨论中,我们将仅专注于 Cython,因为它是专门为 Python 设计的。

Cython 是 Python 的超集,旨在允许我们在 C/C++ 中调用外部编译的代码以及在变量上声明类型,从而显着提高 Python 的性能。

Cython 命令从 Cython 源文件生成优化的 C/C++ 源文件,并将此优化的 C/C++ 源编译为 Python 扩展模块。 它提供了对 NumPy 的内置支持,并将 C 的性能与 Python 的可用性结合在一起。

我们将快速演示如何使用 Cython 大大加快代码速度。 让我们定义一个简单的斐波那契函数:

In [17]: def fibonacci(n):
 a,b=1,1
 for i in range(n):
 a,b=a+b,a
 return a
In [18]: fibonacci(100)
Out[18]: 927372692193078999176L
In [19]: %timeit fibonacci(100)
 100000 loops, best of 3: 18.2 µs per loop

使用timeit模块,我们看到每个循环花费 18.2 µs。

现在让我们在 Cython 中重写函数,通过使用以下步骤为变量指定类型:

  1. 首先,我们将 Cython 魔术函数导入 IPython,如下所示:

    In [22]: %load_ext cythonmagic
    
    
  2. 接下来,我们在 Cython 中重写函数,为变量指定类型:

    In [24]: %%cython
     def cfibonacci(int n):
     cdef int i, a,b
     for i in range(n):
     a,b=a+b,a
     return a
    
    
  3. 让我们来计时一下新的 Cython 函数:

    In [25]: %timeit cfibonacci(100)
     1000000 loops, best of 3: 321 ns per loop
    
    In [26]: 18.2/0.321
    Out[26]: 56.69781931464174
    
    
  4. 因此,我们可以看到 Cython 版本比纯 Python 版本快 57 倍!

有关使用 Cython/SWIG 或其他选项编写 Python 扩展的更多参考,请参考以下参考:

总结

总结本章,我们浏览了 Pandas 的库层次结构,试图说明库的内部运作方式。 我们还谈到了使用 Python 扩展模块来提高代码性能的好处。

posted @ 2025-10-26 09:00  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报