精通 Pandas:1~5

2023-04-23 10:23:30 浏览数 (1)

一、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:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HY8mj5nh-1681366172510)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/images_00002.jpeg)]

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 文件中,我们希望读取的数据如下所示:

代码语言:javascript复制
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 中,我们必须编写以下代码:

代码语言:javascript复制
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,只需两行代码:

代码语言:javascript复制
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 更为精通才能变得更加有效。 本书旨在帮助用户实现这一目标。

二、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。 如果不确定,请在命令提示符下键入以下内容:

代码语言:javascript复制
 which python

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

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

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

代码语言:javascript复制
python --version

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

Debian/Ubuntu (14.04)

代码语言:javascript复制
 sudo apt-get install Python 2.7
 sudo apt-get install Python 2.7-devel

Debian Python 页面位于这里。

Redhat Fedora/Centos/RHEL

代码语言:javascript复制
 sudo yum install python
 sudo yum install python-devel

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

OpenSuse

代码语言:javascript复制
 sudo zypper install python
 sudo zypper install python-devel

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

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

从压缩 tarball 安装 Python

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

代码语言:javascript复制
#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:Python27C:Python27ToolsScripts

第三方 Python 软件安装

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

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

然后,将其保存到C:Python27ToolsScripts

然后,运行ez_setup.pyC:Python27ToolsScriptsez_setup.py

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

然后,从以下位置运行它:C:Python27ToolsScriptsget-pip.py

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

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

  • Enthought:https://enthought.com/
  • Continuum:http://www.continuum.io/
  • Active Python:http://www.activestate.com/activepython

MacOSX

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

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

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

使用包管理器进行安装

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

安装 Homebrew 并运行:

代码语言:javascript复制
ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

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

在 Unix 提示符下安装 Python 2.7:

代码语言:javascript复制
brew install python

安装第三方软件:分发并点子。 安装 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

执行以下步骤进行安装:

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

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

代码语言:javascript复制
bash <Linux installer file>

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

接受许可条款。

指定安装位置。 我倾向于在本地第三方软件安装中使用$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 安装中,我具有以下环境变量设置:

代码语言:javascript复制
ANACONDA_HOME=$HOME/local/anaconda

在 Windows 上,如下所示:

代码语言:javascript复制
set ANACONDA_HOME=C:Anaconda

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

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

代码语言:javascript复制
source $HOME/local/anaconda/bin/activate $ANACONDA_HOME

为 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/

BeautifulSoup和html5lib,lxml中的一个

可选

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 环境,运行以下命令:

代码语言:javascript复制
sudo apt-get install python-dev

RedHat

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

代码语言:javascript复制
yum install python-dev

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

Ubuntu/Debian

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

代码语言:javascript复制
sudo apt-get install python-pandas

Fedora

对于 Fedora,运行以下命令:

代码语言:javascript复制
sudo yum install python-pandas

OpenSuse

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

代码语言:javascript复制
sudo zypper install python-pandas

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

代码语言:javascript复制
sudo yum install gcc-gfortran gcc44-gfortran libgfortran lapack blas python-devel
sudo python-pip install numpy

MacOSX

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

源码安装

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

安装easy_install程序:

代码语言:javascript复制
wget http://python-distribute.org/distribute_setup.pysudo python distribute_setup.py

安装 Cython

代码语言:javascript复制
sudo easy_install -U Cython

然后,您可以从源代码进行安装,如下所示:

代码语言:javascript复制
      git clone git://github.com/pydata/pandas.git
      cd pandas
      sudo python setup.py install

二进制安装

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

代码语言:javascript复制
pip install pandas

Windows

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

二进制安装

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

对于python-dateutil

代码语言:javascript复制
C:Python27Scriptspip install python-dateutil

对于pytz

代码语言:javascript复制
C:Python27Scriptspip install pytz 

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

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

代码语言:javascript复制
pandas-0.16.1-cp26-none-win_amd64.whl (md5)

通过pip安装下载的文件:

代码语言:javascript复制
pip install  
pandas-0.16.1-cp26-none-win_amd64.whl

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

代码语言:javascript复制
import pandas

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

源码安装

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

  1. 按照标题为附录:在 Windows 上安装MinGW编译器的文档中的说明。
  2. 确保将MingW二进制位置添加到附加了C:MingWbinPATH变量中。
  3. 安装CythonNumpy。 可以从这里下载并安装Numpy。 可以从这里下载和安装Cython

安装Cython的步骤如下:

通过 PIP 安装:

代码语言:javascript复制
C:Python27Scriptspip install Cython

直接下载:

从 GitHub 下载并安装 pandas 源代码。

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

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

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

代码语言:javascript复制
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 笔记本的文档。 这是一个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QkIpAso3-1681366172512)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/images_00003.jpeg)]

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

IPython 安装

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

Linux

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

对于 Ubuntu/Debian,请使用

代码语言:javascript复制
sudo apt-get install ipython-notebook

对于 Fedora,请使用

代码语言:javascript复制
sudo yum install python-ipython-notebook

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

代码语言:javascript复制
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 进行安装。 步骤如下:

代码语言:javascript复制
brew install readline
brew install zeromq
pip install ipython pyzmq tornado pygments

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

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

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

代码语言:javascript复制
conda update conda
conda update ipython

Continuum Analytics 的 Wakari

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

Virtualenv

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

Virtualenv 的安装和使用

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

通过pip安装:

代码语言:javascript复制
pip install virtualenv

使用 Virtualenv

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

代码语言:javascript复制
 virtualenv newEnv

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

代码语言:javascript复制
 source newEnv/bin/activate

使用以下命令停用虚拟环境并返回到标准 Python 环境:

代码语言:javascript复制
 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构造器创建:

代码语言:javascript复制
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给出:

代码语言:javascript复制
In [5]: ar2.shape
Out[5]: (2, 3)

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

代码语言:javascript复制
In [7]: ar2.ndim
Out[7]: 2

numpy.arange和 NumPy 数组

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

代码语言:javascript复制
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在起点和终点之间生成线性均匀间隔的元素:

代码语言:javascript复制
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

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

代码语言:javascript复制
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

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

代码语言:javascript复制
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

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

代码语言:javascript复制
In [17]:# Produces identity matrix
 ar9 = np.eye(3);ar9
Out[17]: array([[ 1.,  0.,  0.],
 [ 0.,  1.,  0.],
 [ 0.,  0.,  1.]])
numpy.diag

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

代码语言:javascript复制
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

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

代码语言:javascript复制
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)是一种更便宜,更快捷的分配数组的方法。 但是,只有在确定所有元素稍后都会初始化时,才应使用它:

代码语言:javascript复制
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函数允许通过根据参数重复几次来从较小的数组构造一个数组:

代码语言:javascript复制
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参数指定数字数组的内容类型:

代码语言:javascript复制
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是数组中最长字符串的长度:

代码语言:javascript复制
In [56]: sar=np.array(['Goodbye','Welcome','Tata','Goodnight']); sar.dtype
Out[56]: dtype('S9')

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

代码语言:javascript复制
In [57]: bar=np.array([True, False, True]); bar.dtype
Out[57]: dtype('bool')

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

代码语言:javascript复制
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 序列中一样:

代码语言:javascript复制
# 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惯用法反转数组,如下所示:

代码语言:javascript复制
In [24]: ar=np.arange(5); ar[::-1]
Out[24]: array([4, 3, 2, 1, 0])

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

代码语言:javascript复制
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

代码语言:javascript复制
In [75]: ar[1,1]=5; ar
Out[75]: array([[ 2,  3,  4],
 [ 9,  5,  7],
 [11, 12, 13]])

检索第 2 行:

代码语言:javascript复制
In [76]:  ar[2]
Out[76]: array([11, 12, 13])
In [77]: ar[2,:]
Out[77]: array([11, 12, 13])

检索列 1:

代码语言:javascript复制
In [78]: ar[:,1]
Out[78]: array([ 3,  5, 12])

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

代码语言:javascript复制
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]

代码语言:javascript复制
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值,则需要超过它,如下所示:

代码语言:javascript复制
In [86]: ar[1:6:2]
Out[86]: array([ 2,  6, 10])

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

代码语言:javascript复制
In [91]: ar[:4]
Out[91]: array([0, 2, 4, 6])

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

从元素 4 开始直到结束:

代码语言:javascript复制
In [92]: ar[4:]
Out[92]: array([ 8, 10])

stepValue=3的切片数组:

代码语言:javascript复制
In [94]: ar[::3]
Out[94]: array([0, 6])

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BvlG65lH-1681366172512)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/images_00004.jpeg)]

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

  • 表达式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]])。

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

代码语言:javascript复制
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 数组可用作遮罩,以选择或滤除原始数组的元素。 例如,请参见以下代码段:

代码语言:javascript复制
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'作为默认国家/地区。 请注意,''也是一个空字符串:

代码语言:javascript复制
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类型的数组。 以下代码段对此进行了说明:

代码语言:javascript复制
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 的元素。 现在,假设我们将其更改为以下内容:

代码语言:javascript复制
In [175]: ar[1,3,4,2,7]

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

代码语言:javascript复制
IndexError          Traceback (most recent call last)
<ipython-input-175-adbcbe3b3cdc> in <module>()
----> 1 ar[1,3,4,2,7]

IndexError: too many indices

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

代码语言:javascript复制
In [176]: ar[[1,3]]=50; ar
Out[176]: array([ 0, 50, 22, 50, 44, 55, 66, 77, 88, 99])

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

复杂索引

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

代码语言:javascript复制
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中的元素替换它们,如下所示:

代码语言:javascript复制
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方法可用于查看两个数组是否共享同一存储块。 但是,应谨慎使用,因为它可能会产生误报。 修改视图会修改原始数组:

代码语言:javascript复制
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函数。 正如我们在以下数组中看到的,修改复制的数组时,原始数组不受影响:

代码语言:javascript复制
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 中的各种操作。

基本操作

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

代码语言:javascript复制
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.])

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

代码语言:javascript复制
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])

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

代码语言:javascript复制
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 语句或表达式,其解释如下:

代码语言:javascript复制
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

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

代码语言:javascript复制
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]])

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

代码语言:javascript复制
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)也是按元素排列的:

代码语言:javascript复制
In [244]: ar=np.array([np.pi, np.pi/2]); np.sin(ar)
Out[244]: array([  1.22464680e-16,   1.00000000e 00])

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

代码语言:javascript复制
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 数组可以如下进行转置:

代码语言:javascript复制
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运算符实现以下目标:

代码语言:javascript复制
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。 前面的表达式等效于以下内容:

代码语言:javascript复制
In [24]: np.all(ar==ar2)
Out[24]: True

归约操作

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

代码语言:javascript复制
In [257]: ar=np.arange(1,5)
 ar.prod()
Out[257]: 24

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

代码语言:javascript复制
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])

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

代码语言:javascript复制
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()

代码语言:javascript复制
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:

代码语言:javascript复制
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 组合形状不完全相同的数组的功能。 这是一个例子:

代码语言:javascript复制
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的每一行中,从而产生广播。 这是另一个示例,显示广播在各个维度上均有效:

代码语言:javascript复制
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()函数允许您按以下方式展平多维数组:

代码语言:javascript复制
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返回视图的同时返回副本。

重塑

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

代码语言:javascript复制
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函数:

代码语言:javascript复制
In [408]: ar=np.arange(5); ar.resize((8,));ar
Out[408]: array([0, 1, 2, 3, 4, 0, 0, 0])

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

代码语言:javascript复制
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函数:

代码语言:javascript复制
In [38]: np.resize(ar,(8,))
Out[38]: array([0, 1, 2, 3, 4, 0, 1, 2])

添加大小

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

代码语言:javascript复制
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]])

数组排序

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

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

代码语言:javascript复制
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]])

在这里,我们将解释沿 x 轴的排序:

代码语言:javascript复制
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]])

按就地(np.array.sort)和就地(np.sort)函数排序。

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

  • 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 数组和一个标签数组组成。

序列创建

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

代码语言:javascript复制
import pandas as pd 
ser=pd.Series(data, index=idx)

数据可以是以下之一:

  • ndarray
  • Python 字典
  • 标量值
使用numpy.ndarray

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

代码语言:javascript复制
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 个月的序列结构:

代码语言:javascript复制
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 字典

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

代码语言:javascript复制
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,表明它丢失了。 我们将在后面的部分中处理缺失值。

代码语言:javascript复制
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属性在将序列对象组合到数据帧结构等任务中很有用。

使用标量值

对于标量数据,必须提供索引。 将为尽可能多的索引值重复该值。 此方法的一种可能用途是提供一种快速而肮脏的初始化方法,并在以后填充序列结构。 让我们看看如何使用标量值创建序列:

代码语言:javascript复制
In [491]: dogSeries=pd.Series('chihuahua', 
                   index=['breed','countryOfOrigin',
 'name', 'gender'])
 dogSeries
Out[491]: breed              chihuahua
 countryOfOrigin    chihuahua
 name               chihuahua
 gender             chihuahua
 dtype: object

无法提供索引只会导致返回标量值,如下所示:

代码语言:javascript复制
In [494]: dogSeries=pd.Series('pekingese'); dogSeries
Out[494]: 'pekingese'

In [495]: type(dogSeries)
Out[495]: str

序列操作

序列的行为与上一节中讨论的numpy数组的行为非常相似,其中一个警告是切片等操作也会对索引进行切片。

赋值

可以使用类似于字典的方式使用索引标签设置和访问值:

代码语言:javascript复制
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

代码语言:javascript复制
In [506]: stockPriceSeries['MSFT']
KeyError: 'MSFT'

通过显式使用get可以避免此错误,如下所示:

代码语言:javascript复制
In [507]: stockPriceSeries.get('MSFT',np.NaN)
Out[507]: nan

在这种情况下,将默认值np.NaN指定为序列结构中不存在该键时要返回的值。

切片

切片操作的行为与 NumPy 数组相同:

代码语言:javascript复制
In [498]: stockPriceSeries[:4]
Out[498]: GOOG    1180.97
 FB        62.57
 YHOO        NaN
 TWTR      64.50
 dtype: float64

逻辑切片也如下工作:

代码语言:javascript复制
In [500]: stockPriceSeries[stockPriceSeries > 100]
Out[500]: GOOG    1180.97
 AMZN     358.69
 AAPL     500.60
 dtype: float64
其他操作

可以应用算术和统计运算,就像使用 NumPy 数组一样:

代码语言:javascript复制
In [501]: np.mean(stockPriceSeries)
Out[501]: 433.46600000000001
In [502]: np.std(stockPriceSeries)
Out[502]: 410.50223047384287

按元素操作也可以按顺序执行:

代码语言:javascript复制
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

序列的一个重要功能是根据标签自动对齐数据:

代码语言:javascript复制
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:

代码语言:javascript复制
 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对应于行数。

使用序列字典

在这里,我们通过使用序列对象的字典来创建数据帧结构。

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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属性访问行索引标签和列标签:

代码语言:javascript复制
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)生成行标签索引。

代码语言:javascript复制
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的数组。 有关结构化数组的更多信息,请参考这个内容。

代码语言:javascript复制
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
使用序列结构

在这里,我们展示如何从序列结构构造一个数据帧结构:

代码语言:javascript复制
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 数据帧结构。

操作

在这里,我将简要描述各种数据帧操作。

选取

特定的列可以作为序列结构获得:

代码语言:javascript复制
In [543]: memberDF['Name']
Out[543]: 0    Sanjeev
 1    Yingluck
 2    Emeka
 3    Amy
 Name: Name, dtype: object
赋值

可以通过分配添加新列,如下所示:

代码语言:javascript复制
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一样:

代码语言:javascript复制
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

也可以像字典一样弹出它:

代码语言:javascript复制
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函数:

代码语言:javascript复制
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
对齐

数据帧对象以与序列对象相似的方式对齐,只不过它们在列和索引标签上都对齐。 结果对象是列标签和行标签的并集:

代码语言:javascript复制
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填充,例如,铜和金。 如果将数据帧对象和序列对象组合在一起,则默认行为是在各行之间广播序列对象:

代码语言:javascript复制
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
其他数学运算

可以将数学运算符明智地应用于数据帧结构:

代码语言:javascript复制
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 数组构造面板对象。

代码语言:javascript复制
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 字典来构造面板结构。

代码语言:javascript复制
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方法

此方法将具有多重索引的数据帧结构转换为面板结构:

代码语言:javascript复制
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

美国/中国经济数据的来源是以下站点:

  • http://www.multpl.com/us-gdp-inflation-adjusted/table
  • http://www.multpl.com/united-states-population/table
  • http://en.wikipedia.org/wiki/Demographics_of_China
  • http://www.theguardian.com/news/datablog/2012/mar/23/china-gdp-since-1980

其他操作

插入,删除和逐项操作的行为与数据帧相同。 面板结构可以通过转置重新排列。面板的操作功能集相对欠发达,不如序列和数据帧丰富。

总结

总结本章,numpy.ndarray是 Pandas 数据结构所基于的基岩数据结构。 Pandas 的数据结构由 NumPy ndarray数据和一个或多个标签数组组成。

Pandas 中有三种主要的数据结构:序列,数据帧架和面板。 与 Numpy ndarrays相比,pandas 数据结构更易于使用且更加用户友好,因为在数据帧和面板的情况下,它们提供行索引和列索引。数据帧对象是 Pandas 中最流行和使用最广泛的对象。 在下一章中,我们将讨论 Pandas 索引的主题。

四、Pandas 的操作,第一部分 – 索引和选择

在本章中,我们将着重于对来自 Pandas 对象的数据进行索引和选择。 这很重要,因为有效利用 Pandas 需要对索引和选择数据有充分的了解。 我们将在本章中讨论的主题包括:

  • 基本索引
  • 标签,整数和混合索引
  • 多重索引
  • 布尔索引
  • 索引操作

基本索引

在上一章中,我们已经讨论了有关序列和数据帧的基本索引,但是为了完整起见,这里我们将包括一些示例。 在这里,我们列出了根据 IMF 数据得出的 2013 年第 4 季度原油现货价格的时间序列。

代码语言:javascript复制
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

我们可以使用[]运算符为迪拜原油的可用时间段选择价格:

代码语言:javascript复制
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

我们可以将列列表传递给[]运算符,以便以特定顺序选择列:

代码语言:javascript复制
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异常:

代码语言:javascript复制
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运算符并在不存在该列的情况下指定默认值来避免此错误,如下所示:

代码语言:javascript复制
In [650]: SpotCrudePrices_2013.get('Brent Blend','N/A')
Out[650]: 'N/A'

注意

请注意,无法使用数据帧中的括号运算符[]选择行。

因此,在以下情况下会出现错误:

代码语言:javascript复制
In [755]:SpotCrudePrices_2013['2013-Q1']
 --------------------------------------------------
 KeyError        Traceback (most recent call last)
 ...
 KeyError: u'no item named 2013-Q1'

这是创作者为避免歧义而做出的设计决定。 对于序列,没有歧义,可以使用[]运算符选择行:

代码语言:javascript复制
In [756]: dubaiPrices['2013-Q1']
Out[756]: 108.1

我们将在本章后面看到如何使用一种较新的索引运算符执行行选择。

使用点运算符访问属性

可以直接从序列,数据帧或面板中检索值作为属性,如下所示:

代码语言:javascript复制
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 标识符时才有效,如下所示:

代码语言:javascript复制
In [653]: SpotCrudePrices_2013."West Texas Intermediate"
 File "<ipython-input-653-2a782563c15a>", line 1
 SpotCrudePrices_2013."West Texas Intermediate"
 ^
 SyntaxError: invalid syntax

否则,由于列名中的空格,我们将像前面的情况一样获得SyntaxError。 有效的 Python 标识符必须遵循以下词汇约定:

代码语言:javascript复制
identifier::= (letter|"_") (letter | digit | "_")*

因此,有效的 Python 标识符不能包含空格。 有关更多详细信息,请参见 Python 词法分析文档。

我们可以通过重命名列索引名称来解决这些问题,以便它们都是有效的标识符:

代码语言:javascript复制
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 的价格:

代码语言:javascript复制
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 列(英国布伦特)来选择价格,如下所示:

代码语言:javascript复制
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 的语法完全匹配:

代码语言:javascript复制
ar[startIndex: endIndex: stepValue]

如果未指定,则默认值如下:

  • startIndex为 0
  • endIndexarraysize - 1
  • stepValue为 1

对于数据帧,[]跨行切片如下:

获取前两行:

代码语言:javascript复制
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 开始的所有行:

代码语言:javascript复制
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 行开始,以两间隔获取行:

代码语言:javascript复制
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

反转数据帧中的行顺序:

代码语言:javascript复制
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

对于序列,其行为也很直观:

代码语言:javascript复制
In [666]: dubaiPrices=SpotCrudePrices_2013['Dubai']

获取最后三行或除第一行外的所有行:

代码语言:javascript复制
In [681]: dubaiPrices[1:]
Out[681]: 2013-Q2    100.8
 2013-Q3    106.1
 2013-Q4    106.7
 Name: Dubai, dtype: float64

获取除最后一行以外的所有行:

代码语言:javascript复制
In [682]: dubaiPrices[:-1]
Out[682]: 2013-Q1    108.1
 2013-Q2    100.8
 2013-Q3    106.1
 Name: Dubai, dtype: float64

反转行:

代码语言:javascript复制
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'
  • 布尔数组。

对于我们的说明性数据集,我们使用以下城市的平均下雪天气温度数据:

  • http://www.currentresults.com/Weather/New-York/Places/new-york-city-snowfall-totals-snow-accumulation-averages.php
  • http://www.currentresults.com/Weather/New-York/Places/new-york-city-temperatures-by-month-average.php

创建数据帧

代码语言:javascript复制
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

使用单个标签:

代码语言:javascript复制
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

使用标签列表:

代码语言:javascript复制
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

使用标签范围:

代码语言:javascript复制
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运算符时,必须始终首先指定行索引。 这与[]运算符相反,后者只能直接选择列。 因此,如果执行以下操作,则会出现错误:

代码语言:javascript复制
In [771]: NYC_SnowAvgs.loc['Avg SnowDays']

KeyError: 'Avg SnowDays'

正确的方法是使用冒号(:)运算符专门选择所有行,如下所示:

代码语言:javascript复制
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

在这里,我们看到如何选择一个特定的坐标值,即三月份的平均下雪天数:

代码语言:javascript复制
In [732]: NYC_SnowAvgs.loc['March','Avg SnowDays']
Out[732]: 1.7

还支持这种替代样式:

代码语言:javascript复制
In [733]: NYC_SnowAvgs.loc['March']['Avg SnowDays']
Out[733]: 1.7

以下是使用方括号运算符[]的前述情况的等效内容:

代码语言:javascript复制
In [750]: NYC_SnowAvgs['Avg SnowDays']['March']
Out[750]: 1.7

再次注意,但是,首先使用.loc运算符指定行索引值将得到Keyerror。 这是前面讨论的事实的结果,即[]运算符不能用于直接选择行。 必须首先选择列以获得序列,然后可以按行选择。 因此,如果使用以下任一方法,则将获得KeyError: u'no item named March'

代码语言:javascript复制
In [757]: NYC_SnowAvgs['March']['Avg SnowDays']

要么

代码语言:javascript复制
In [758]: NYC_SnowAvgs['March']

我们可以使用.loc运算符来选择行:

代码语言:javascript复制
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

使用布尔数组进行选择

现在,我们将展示如何使用布尔数组选择平均下雪天少于一个的月份:

代码语言:javascript复制
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 美元的原油品牌相对应的列:

代码语言:javascript复制
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

请注意,前面的参数涉及实际计算布尔数组的布尔运算符<>,例如:

代码语言:javascript复制
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

让我们创建以下内容:

代码语言:javascript复制
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

我们可以使用整数切片在前两行中选择非物理常数:

代码语言:javascript复制
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

或者,我们可以在第三行中使用光速和重力加速度:

代码语言:javascript复制
In [795]: sci_values.iloc[2,0:2]
Out[795]: 0    2.997925e 08
 1    9.806650e 00
 dtype: float64

请注意,.iloc的参数严格位于位置,与索引值无关。 因此,请考虑以下情况,我们错误地认为可以通过使用以下命令获得第三行:

代码语言:javascript复制
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,如下所示:

代码语言:javascript复制
In [797]: sci_values.loc[10]
Out[797]: 0    2.997925e 08
 1    9.806650e 00
 2    1.602177e-19
 Name: 10, dtype: float64

要切出特定的行,我们可以使用以下命令:

代码语言:javascript复制
In [802]: sci_values.iloc[2:3,:]
Out[802]:     0          1       2
 10   299792458  9.80665 1.602177e-19

要使用整数位置获取横截面,请使用以下命令:

代码语言:javascript复制
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,如下所示:

代码语言:javascript复制
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运算符可用于快速选择标量值。 最好的说明如下:

代码语言:javascript复制
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)并将其读取来重新创建以下数据帧:

代码语言:javascript复制
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 数据读入数据帧的方法:

代码语言:javascript复制
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运算符:

代码语言:javascript复制
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:

代码语言:javascript复制
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:

代码语言:javascript复制
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

请注意,使用单个标签与使用仅包含单个标签的列表之间的输出差异。 前者产生序列,而后者产生一个数据帧:

代码语言:javascript复制
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 的。 因此,在后一种情况下返回的是一个数据帧。 使用基于标签的切片:

代码语言:javascript复制
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

使用单个整数:

代码语言:javascript复制
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

使用整数列表:

代码语言:javascript复制
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

使用整数切片:

代码语言:javascript复制
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

使用布尔数组:

代码语言:javascript复制
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并读入:

代码语言:javascript复制
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列创建一个多重索引:

代码语言:javascript复制
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函数与适当的参数一起应用将为索引的每个级别生成标签列表:

代码语言:javascript复制
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

代码语言:javascript复制
In [88]: mIndex.get_level_values(2)
 ---------------------------------------------------------
IndexError                      Traceback (most recent call last)
...

您可以使用多重索引的数据帧实现分层索引:

代码语言:javascript复制
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 

我们可以使用多重索引进行切片:

代码语言:javascript复制
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

我们可以尝试在较低级别进行切片:

代码语言:javascript复制
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()方法对多重索引中的轴的标签进行排序。 为了安全起见,在使用多重索引切片之前,请先进行排序。 因此,我们可以执行以下操作:

代码语言:javascript复制
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

我们还可以传递一个元组列表:

代码语言:javascript复制
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函数可在多重索引中交换级别:

代码语言:javascript复制
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函数更通用,允许您指定级别的顺序:

代码语言:javascript复制
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方法提供了一种基于特定索引级别值选择数据的快捷方式:

代码语言:javascript复制
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级别之间切换,然后执行以下选择:

代码语言:javascript复制
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 的交易日期:

代码语言:javascript复制
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

您还可以创建布尔条件,在其中可以使用数组过滤掉部分数据:

代码语言:javascript复制
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的布尔数组。 这使用户可以检查序列中是否存在一个或多个元素。 这是使用序列的插图:

代码语言:javascript复制
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

在这里,我们使用布尔数组选择一个包含我们感兴趣的值的子序列:

代码语言:javascript复制
In [318]: stockSeries[stockSeries.isin(['AMZN','FB'])]
Out[318]: 1    AMZN
 3      FB
 dtype: object

对于我们的数据帧示例,我们切换到一个更有趣的数据集,该数据集是针对那些对人类生物学有偏爱,对澳大利亚哺乳动物进行分类(属于我的宠物)的数据集:

代码语言:javascript复制
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'}
}

有关哺乳动物的更多信息:有袋动物是袋装哺乳动物,单峰类是产卵的,胎盘可生幼年。 该信息的来源是这里。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTZuUx7l-1681366172513)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/images_00005.jpeg)]

上一个图像的来源是 Bennett 的小袋鼠。

代码语言:javascript复制
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

让我们尝试选择澳大利亚本土的哺乳动物:

代码语言:javascript复制
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()方法创建遮罩来获得更好的结果:

代码语言:javascript复制
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,以便用户可以生成如下所示的相同值:

代码语言:javascript复制
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

此方法似乎仅在序列情况下有用,因为在数据帧情况下我们免费获得此行为:

代码语言:javascript复制
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

代码语言:javascript复制
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-允许在现有数据帧上创建索引并返回索引的数据帧。 正如我们之前所见:

代码语言:javascript复制
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

现在,我们可以如下设置索引:

代码语言:javascript复制
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

代码语言:javascript复制
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文件转换为数据帧:

代码语言:javascript复制
In [27]: uefaDF=pd.read_csv('./euro_winners.csv')
In [28]: uefaDF.head()
Out[28]:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLuHRTiV-1681366172514)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/images_00006.jpeg)]

因此,输出显示了赛季,获胜和亚军俱乐部所属的国家,得分,场地和出勤人数。 假设我们要按获得的欧洲俱乐部冠军的数量来对各国进行排名。 我们可以使用groupby来做到这一点。 首先,我们将groupby应用于数据帧并查看结果的类型是什么:

代码语言:javascript复制
In [84]: nationsGrp =uefaDF.groupby('Nation');
 type(nationsGrp)
Out[84]: pandas.core.groupby.DataFrameGroupBy

因此,我们看到nationsGrppandas.core.groupby.DataFrameGroupBy类型。 我们在其中使用groupby的列称为键。 我们可以通过在生成的DataFrameGroupBy对象上使用groups属性来查看组的外观:

代码语言:javascript复制
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()函数获得:

代码语言:javascript复制
In [109]: len(nationsGrp.groups)
Out[109]: 10

现在,我们可以通过将size()函数应用于该组,然后应用sort()函数(按位置排序),以降序显示每个国家的获胜次数:

代码语言:javascript复制
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函数:

代码语言:javascript复制
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
  • 西班牙甲级联赛或西甲
  • 意大利甲级联赛
  • 德国超级联赛或德甲联赛

此信息的来源位于这里。

现在让我们像往常一样将目标统计数据读入数据帧中。 在这种情况下,我们使用月份在数据帧上创建一个行索引:

代码语言:javascript复制
In [68]: goalStatsDF=pd.read_csv('./goal_stats_euro_leagues_2012-13.csv')
 goalStatsDF=goalStatsDF.set_index('Month')

我们看一下数据集前端和后端的快照:

代码语言:javascript复制
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显示统计信息,但这将按年份分组。 这是如何完成的:

代码语言:javascript复制
In [117]: goalStatsGroupedByYear = goalStatsDF.groupby(
lambda Month: Month.split('/')[2])

然后,我们可以遍历生成的groupby对象并显示组。 在以下命令中,我们看到按年份分组的两组统计信息。 请注意,使用 lambda 函数从月份的第一天开始获取年份组。 有关 lambda 函数的更多信息,请转到这里:

代码语言:javascript复制
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与级别参数一起应用,如下所示:

代码语言:javascript复制
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

代码语言:javascript复制
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组成的多重索引的目标统计数据:

代码语言:javascript复制
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

假设我们希望计算每个联赛的进球总数和整个赛季的总比赛数,我们可以这样做:

代码语言:javascript复制
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并将级别作为参数传递,可以获得与前一个结果相同的结果:

代码语言:javascript复制
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

现在,让我们获取一个关键统计数据,以确定每个联赛中本赛季的兴奋程度 - 每场比赛的进球数比:

代码语言:javascript复制
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

如上一条命令所示,它作为序列返回。 现在,我们可以显示每场比赛的进球数,进球数和比赛数,以概述联盟的兴奋程度,如下所示:

获得每个游戏数据的目标作为数据帧。 请注意,由于gpg作为序列返回,因此我们必须对其进行转置:

代码语言:javascript复制
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

重新索引goalsPerGameDF数据帧,以便将0索引替换为GoalsPerGame

代码语言:javascript复制
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

goalsPerGameDF数据帧追加到原始数据帧:

代码语言:javascript复制
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 年欧洲联赛每场比赛的进球数。 可以在这个链接中找到其来源。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUCG4hRU-1681366172514)(https://gitcode.net/apachecn/apachecn-ds-zh/-/raw/master/docs/master-pandas/img/images_00007.jpeg)]

使用聚合方法

生成摘要统计信息的另一种方法是显式使用聚合方法:

代码语言:javascript复制
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,因此由于上一节中的格式设置,整数值数据将不会显示为浮点。

应用多种函数

对于分组的数据帧对象,我们可以指定要应用于每列的函数列表:

代码语言:javascript复制
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示例,并计算锦标赛获胜者所在国家/地区的出勤率统计数据:

代码语言:javascript复制
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对象相同的大小。 让我们考虑一个数据帧架,该数据帧架显示四个足球联赛中每个月的得分目标:

代码语言:javascript复制
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值。 让我们按年份对数据进行分组:

代码语言:javascript复制
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变量并采用结果列表的第三个元素来获取年份。

如果我们计算各个联赛中每年举行比赛的月份数,那么我们有:

代码语言:javascript复制
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方法应用此转换:

代码语言:javascript复制
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:

代码语言:javascript复制
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 个进球:

代码语言:javascript复制
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函数:

代码语言:javascript复制
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的工作原理:

代码语言:javascript复制
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

现在,我们获取各种数据片段:

代码语言:javascript复制
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来包括所有列均不具有值的条目:

代码语言:javascript复制
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 

我们还可以指定一个内部连接来进行连接,但是通过丢弃缺少列的行来只包含包含最终数据帧中所有列值的行,也就是说,它需要交集:

代码语言:javascript复制
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

第三种情况使我们能够使用原始数据帧中的特定索引进行连接:

代码语言:javascript复制
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

代码语言:javascript复制
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数据帧的前两行和前三列切成薄片:

代码语言:javascript复制
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

其余的行:

代码语言:javascript复制
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合并来自前面命令的两个数据帧:

代码语言:javascript复制
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函数:

代码语言:javascript复制
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方法来将单个行附加到数据帧:

代码语言:javascript复制
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 表。 以下命令对此进行了说明:

代码语言:javascript复制
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中来研究合并的使用:

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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参数的值:

代码语言:javascript复制
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参数的值:

代码语言:javascript复制
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',如以下命令所示:

代码语言:javascript复制
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'

代码语言:javascript复制
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函数用于合并两个具有不同列且没有共同点的数据帧。 本质上,这是两个数据帧的纵向连接。 这是一个例子:

代码语言:javascript复制
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运算符称为:

代码语言:javascript复制
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两个具有共同列的数据帧一起使用时会发生什么:

代码语言:javascript复制
In [272]: slice1.join(slice2)
------------------------------------------------------------
Exception                  Traceback (most recent call last)
...

Exception: columns overlap: Index([u'TradingDate'], dtype=object)

由于列重叠而导致异常。 您可以在官方文档页面中找到有关使用mergeconcatjoin操作的更多信息。

数据透视和重塑

本节介绍如何重塑数据。 有时,数据以堆叠的格式存储。 这是使用PlantGrowth数据集的堆叠数据的示例:

代码语言:javascript复制
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)下获得的植物的干重产量的实验结果。 假设我们想按组值对该数据进行一些分析。 一种方法是在数据帧上使用逻辑过滤器:

代码语言:javascript复制
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函数执行以下操作:

代码语言:javascript复制
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函数可以达到相同的结果,如下所示:

代码语言:javascript复制
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 个观测值中每个组的平均值,我们将执行以下操作,这将得出一个序列:

代码语言:javascript复制
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()函数

首先,我们将组和观察列的值分别设置为行索引的组成部分,从而得到多重索引:

代码语言:javascript复制
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级别会发生什么:

代码语言:javascript复制
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)

在这里,我们可以看到数据帧已旋转,并且该组现在已从行索引(标题)更改为列索引(标题),从而使数据帧看起来更加紧凑。 为了更详细地了解正在发生的事情,我们首先将多重索引作为行索引放在组的观察上:

代码语言:javascript复制
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)

取消堆叠操作从行索引中删除组,将其更改为单级索引:

代码语言:javascript复制
In [357]: plantGrowthStackedDF.unstack(level='group').index
Out[357]: Int64Index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=int64)

现在,多重索引在列上:

代码语言:javascript复制
In [352]: plantGrowthStackedDF.unstack(level='group').columns
Out[352]: MultiIndex
 [(u'weight', u'ctrl'), (u'weight', u'trt1'), (u'weight', u'trt2')]

让我们看看调用反向操作stack时会发生什么:

代码语言:javascript复制
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,如下所示:

代码语言:javascript复制
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()函数将堆叠级别设置为结果行中的多重索引中的最低级别:

代码语言:javascript复制
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的使用:

代码语言:javascript复制
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()函数

此函数用于将分类变量转换为指标数据帧,该指标本质上是分类变量可能值的真值表。 下面的命令是一个示例:

代码语言:javascript复制
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,例如处理时间序列数据以及如何处理数据中的缺失值。

要获得有关这些主题的更多信息,请访问官方文档。

0 人点赞