第 4 章 pandas库简介

第 4 章 pandas库简介

你终于要走进本书的心脏了:pandas库。这个库是用Python语言分析数据的好工具。

你首先将学习这个库的基础知识、安装方法,最后将熟悉Series(序列)和DataFrame(数据框)这两种数据结构。在学习本章的过程中,你将学着使用pandas库的几个基础函数处理最常见的数据分析任务。熟悉这些操作对本书后续内容的学习起着至关重要的作用。因此,很有必要多重复几遍从本章学到的所有技能,直到熟练掌握。

我们还将通过多个例子讲解pandas库采用的新概念:它的数据结构所使用的索引机制。至于如何充分利用索引机制处理数据,这一章和接下来几章会作讲解。

本章最后,我们来看一下如何将索引机制这个概念同时扩展到多层:层级索引。

4.1 pandas:Python数据分析库

pandas是一个专门用于数据分析的开源Python库。目前,所有使用Python语言研究和分析数据集的专业人士,在做相关统计分析和决策时,pandas都是他们的基础工具。

2008年,Wes McKinney一人挑起了pandas库的设计和开发工作;2012年,他的同事Sien Chang加入开发。他俩一起开发出了Python社区最为有用的库之一——pandas。

数据分析工作需要一个专门的库,它能够以最简单的方式提供数据处理、数据抽取和数据操作所需的全部工具,开发pandas正是为了满足这个需求。

Wes McKinney选择以NumPy库作为Python库pandas的基础进行设计。可以说,该选择对于pandas的成功和它的迅速扩展起着至关重要的作用。事实上,选择以NumPy为基础,不仅使pandas能和其他大多数模块相兼容,而且还能借力NumPy模块在计算方面性能高的优势。

另外一个意义深远的决定是为数据分析专门设计了两种数据结构。实际情况是,pandas没有使用Python已有的内置数据结构,也没有使用其他库的数据结构,而是开发了两种新型的数据结构。

这两种数据结构的设计初衷是用于关系型或带标签的数据。用它们管理与SQL关系数据库和Excel工作表具有类似特征的数据很方便。

本书会讲到一系列数据分析的基础操作,操作对象通常为数据库表或工作表。pandas提供多个函数和方法用于数据分析,在很多情况下,它们是执行这些操作的最佳方法。

因此,pandas的主要目的是为每一位数据分析人士提供所有的基础工具。

4.2 安装

安装pandas库最简单和最常用的方法是先安装一个发行版,例如,先安装Anaconda或Enthought,再用发行版安装pandas。

4.2.1 用Anaconda安装

对于选用Anaconda发行版的读者,安装pandas很简单。首先,我们来看一下pandas是否已经安装,安装的版本号是多少。为此,在终端输入以下命令:

conda list pandas

因为我事先在我的个人计算机(Windows系统)上安装了pandas库,所以得到如下输出结果:

# packages in environment at C:\Users\Fabio\Anaconda:
#
pandas                    0.14.1               np19py27_0

如果你运行上述命令时的输出结果不是这样,就需要安装pandas库。请输入以下命令:

conda install pandas

Anaconda立即检查它所有的依赖库,管理其他模块的安装,为你省心不少。

如果想更新pandas库,命令也很简单直接:

conda update pandas

Anaconda会检查pandas以及所有依赖模块的版本,如有要更新的,即予以提示,然后询问你是否想更新。

Fetching package metadata: ....
Solving package specifications: .
Package plan for installation in environment C:\Users\Fabio\Anaconda:

Anaconda将会下载下面这些包。

Package                    |           build
---------------------------|--------------------------------
pytz-2014.9                |          py27_0          169 KB
requests-2.5.3             |          py27_0          588 KB
six-1.9.0                  |          py27_0           16 KB
conda-3.9.1                |          py27_0          206 KB
pip-6.0.8                  |          py27_0          1.6 MB
scipy-0.15.1               |      np19py27_0         71.3 MB
pandas-0.15.2              |      np19py27_0          4.3 MB
------------------------------------------------------------
                                      Total:         78.1 MB

Anaconda将会更新下面这些包。

conda:    3.9.0-py27_0      --> 3.9.1-py27_0
pandas:   0.14.1-np19py27_0 --> 0.15.2-np19py27_0
pip:      1.5.6-py27_0      --> 6.0.8-py27_0
pytz:     2014.7-py27_0     --> 2014.9-py27_0
requests: 2.5.1-py27_0      --> 2.5.3-py27_0
scipy:    0.14.0-np19py27_0 --> 0.15.1-np19py27_0
six:      1.8.0-py27_0      --> 1.9.0-py27_0

Proceed ([y]/n)?

敲入y键后,Anaconda开始从网上下载所有新的模块。因此,如要执行这一步,计算机需联网。

Fetching packages ...
scipy-0.15.1-n 100% |###############################| Time: 0:01:11   1.05 MB/s
Extracting packages ...
[      COMPLETE      ] |#################################################| 100%
Unlinking packages ...
[      COMPLETE      ] |#################################################| 100%
Linking packages ...
[      COMPLETE      ] |#################################################| 100%

4.2.2 用PyPI安装

还可以从PyPI安装pandas:

pip install pandas

4.2.3 在Linux系统的安装方法

如果你用的是某一Linux发行版,且不打算使用打包好的Python发行版,可以像安装其他包那样安装pandas。

Debian和Ubuntu Linux系统:

sudo apt-get install python-pandas

OpenSuse和Fedora Linux系统,则需要使用以下命令:

zypper in python-pandas

4.2.4 用源代码安装

如果你想通过编译源代码来安装pandas,请参考以下GitHub链接:http://github.com/pydata/pandas

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

编译前,确保已经安装Cython。更多信息请参考包括官方文档(http://pandas.pydata.org/pandas-docs/stable/install.html)在内的在线文档。

4.2.5 Windows模块仓库

Windows系统用户如果喜欢自己管理模块,以便总是使用最新模块,可以从网上模块仓库下载很多第三方模块:Christoph Gohlke的Windows系统Python扩展包仓库www.lfd.uci.edu/~gohlke/pythonlibs/)。每个模块都提供32位和64位WHL(wheel)格式的安装包。如果要安装模块,需要使用pip命令(参见第2章)。

pip install SomePackege-1.0.whl

选择模块时,注意选择与Python版本和计算机系统相兼容的版本。此外,虽然NumPy不依赖其他包,但是pandas依赖多个包。要确保安装所有的依赖包,不过安装顺序并不重要。

这种方法的缺点是,每个包要单独安装,没有包管理器来帮你管理版本和依赖;但优点是,对这些模块及其版本有更大的控制权,你不用使用Anaconda等发行版提供的包,而是使用最新的包。

4.3 测试pandas是否安装成功

pandas库还提供一项功能,安装完成后,可运行测试,检查内部命令是否能够执行(官方文档表示,所有内部代码的测试覆盖率高达97%)。

首先确保Python发行版安装了nose模块(请见下面“nose模块”的介绍)。如已安装,输入以下命令开始测试:

nosetests pandas

测试任务需要花费几分钟时间,测试完成后,将显示问题列表。

nose模块

nose模块是用来在项目开发阶段,特别是Python模块的开发阶段,测试Python代码的。这个模块扩展了unittest模块的功能,Python的unittest模块是用来测试代码的。与其相比,nose模块简化了测试代码,降低了它的编写难度。

我建议你读读这篇文章:http://pythontesting.net/framework/nose/nose-introduction/

4.4 开始pandas之旅

本章的主题是解释数据结构和用来处理这些数据结构的相关函数/方法,所以不需要编写大段代码。

因此,我认为学习本章的好方法是打开Python shell,逐条输入命令。这样,你就有机会熟悉本章依次讲解的单个函数和数据结构。

此外,本章前面例子中定义的数据和函数在后面仍然有效,无需每次重复定义。每个例子结束后,你最好重复练习各条命令,可做些适当的修改,在操作过程中留意如何操纵数据结构中的数据。这种方法非常适合用来熟悉本章讲解的几项主要内容,你可以尽情地以交互式方式探索命令的作用,从而避免机械地编写和执行代码。

注意 本章假定你多少熟悉Python和NumPy。如遇到任何困难,请阅读前面的第2章和第3章。

首先,在Python shell打开一段会话,导入pandas库。pandas的常用导入方法如下:

>>> import pandas as pd
>>> import numpy as np

因此,本书中从此往后,再见到pdnp时,它们分别指的是与pandas和NumPy这两个库相关的对象或方法,即使你可能经常想使用下面这种方法导入pandas模块:

>>> from pandas import *

这样,你就无需用pd指定函数、对象或方法。然而,通常来说,这种方法在Python社区看来是不太好的。

4.5 pandas数据结构简介

pandas的核心为两大数据结构,数据分析相关的所有事务都是围绕着这两种结构进行的:

  • Series
  • DataFrame

后面也会讲,Series这类数据结构用于存储一个序列这样的一维数据,而DataFrame作为更复杂的数据结构,则用于存储多维数据。

虽然这些数据结构不能解决所有问题,但它们为大多数应用提供了有效和强大的工具。就简洁性而言,它们理解和使用起来都很简单。此外,很多更为复杂的数据结构都可以追溯到这两种结构。

然而,两者的奇特之处是将Index(索引)对象和标签整合到自己的结构中。后面你将会感受到,该特点使得这两种数据结构具有很强的可操作性。

4.5.1 Series对象

pandas库的Series对象用来表示一维数据结构,跟数组类似,但多了一些额外的功能。它的内部结构很简单(见图4-1),由两个相互关联的数组组成,其中主数组用来存放数据(NumPy任意类型数据)。主数组的每个元素都有一个与之相关联的标签,这些标签存储在另外一个叫作Index的数组中。

图4-1 Series对象的结构

  1. 声明Series对象

    调用Series()构造函数,把要存放在Series对象中的数据以数组形式传入,就能创建一个如图4-1所示的Series对象。

    >>> s = pd.Series([12,-4,7,9])
    >>> s
    0    12
    1    -4
    2     7
    3     9
    dtype: int64

    从Series的输出可以看到,左侧Index是一列标签,右侧是标签对应的元素。

    声明Series时,若不指定标签,pandas默认使用从0开始依次递增的数值作为标签。这种情况下,标签与Series对象中元素的索引(在数组中的位置)一致。

    然而,最好使用有意义的标签,用以区分和识别每个元素,而不用考虑元素插入到Series中的顺序。

    因此,调用构造函数时,就需要指定index选项,把存放有标签的数组赋给它,其中标签为字符串类型。

    >>> s = pd.Series([12,-4,7,9], index=['a','b','c','d'])
    >>> s
    a    12
    b    -4
    c     7
    d     9
    dtype: int64

    如果想分别查看组成Series对象的两个数组,可像下面这样调用它的两个属性:index(索引)和values(元素)。

    >>> s.values
    array([12, -4, 7, 9], dtype=int64)
    >>> s.index
    Index([u'a', u'b', u'c', u'd'], dtype='object')
  2. 选择内部元素

    若想获取Series对象内部的元素,把它作为普通的NumPy数组,指定键即可。

    >>> s[2]
    7

    或者,指定位于索引位置处的标签。

    >>> s['b']
    -4

    跟从NumPy数组选择多个元素的方法相同,可像下面这样选取多项:

    >>> s[0:2]
    A    12
    b    -4
    dtype: int64

    或者,这种情况甚至可以使用元素对应的标签,只不过要把标签放到数组中:

    >>> s[['b','c']]
    b   -4
    c    7
    dtype: int64
  3. 为元素赋值

    既然你已理解单个元素的选取方法,赋值方法也就不言自明。可以用索引或标签选取元素后进行赋值。

    >>> s[1] = 0
    >>> s
    a   12
    b    0
    c    7
    d    9
    dtype: int64
    >>> s['b'] = 1
    >>> s
    a    12
    b     1
    c     7
    d     9
    dtype: int64
  4. 用NumPy数组或其他Series对象定义新Series对象

    你可以用NumPy数组或现有的Series对象定义新的Series对象。

    >>> arr = np.array([1,2,3,4])
    >>> s3 = pd.Series(arr)
    >>> s3
    0    1
    1    2
    2    3
    3    4
    dtype: int32
    
    >>> s4 = pd.Series(s)
    >>> s4
    a    12
    b     4
    c     7
    d     9
    dtype: int64

    然而,这样做时不要忘记新Series对象中的元素不是原NumPy数组或Series对象元素的副本,而是对它们的引用。也就是说,这些对象是动态插入到新Series对象中。如改变原有对象元素的值,新Series对象中这些元素也会发生改变。

    >>> s3
    0    1
    1    2
    2    3
    3    4
    dtype: int32
    
    >>> arr[2] = -2
    >>> s3
    0    1
    1    2
    2   -2
    3    4
    dtype: int32

    上述例子,改动arr数组第三个元素的值,同时也会修改Series对象s3中相应的元素。

  5. 筛选元素

    pandas库的开发是以NumPy库为基础的,因此就数据结构而言,NumPy数组的多种操作方法得以扩展到Series对象中,其中就有根据条件筛选数据结构中的元素这一方法。

    如要获取Series对象中所有大于8的元素,可使用以下代码:

    >>> s[s > 8]
    a    12
    d     9
    dtype: int64
  6. Series对象运算和数学函数

    适用于NumPy数组的运算符(+-*/)或其他数学函数,也适用于Series对象。

    至于运算符,直接用来编写算术表达式即可。

    >>> s / 2
    a    6.0
    b   -2.0
    c    3.5
    d    4.5
    dtype: float64

    然而,至于NumPy库的数学函数,必须指定它们的出处np,并把Series实例作为参数传入。

    >>> np.log(s)
    a    2.484907
    b         NaN
    c    1.945910
    d    2.197225
    dtype: float64
  7. Series对象的组成元素

    Series对象往往包含重复的元素,你很可能想知道里面都包含哪些元素,统计元素重复出现的次数或判断一个元素是否在Series中。

    我们来声明一个包含多个重复元素的Series对象。

    >>> serd = pd.Series([1,0,2,1,2,3], index=['white','white','blue','green','green','yellow'])
    >>> serd
    white     1
    white     0
    blue      2
    green     1
    green     2
    yellow    3
    dtype: int64

    要弄清楚Series对象包含多少个不同的元素,可使用unique()函数。其返回结果为一个数组,包含Series去重后的元素,但顺序看上去很随意。

    >>> serd.unique()
    array([1, 0, 2, 3], dtype=int64)

    unique()函数相似的另外一个函数是value_counts()函数,它不仅返回各个不同的元素,还计算每个元素在Series中的出现次数。

    >>> serd.value_counts()
    2    2
    1    2
    3    1
    0    1
    dtype: int64

    最后,isin()函数用来判断所属关系,也就是判断给定的一列元素是否包含在数据结构之中。isin()函数返回布尔值,可用于筛选Series或DataFrame列中的数据。

    >>> serd.isin([0,3])
    white     False
    white      True
    blue      False
    green     False
    green     False
    yellow     True
    dtype:     bool
    >>> serd[serd.isin([0,3])]
    white     0
    yellow    3
    dtype: int64
  8. NaN

    在前面的一个例子中,我们求负数的对数,得到的返回结果为NaN(Not a Number,非数值)。数据结构中若字段为空或者不符合数字的定义时,用这个特定的值来表示。

    一般来讲,NaN值表示数据有问题,必须对其进行处理,尤其是在数据分析时。从某些数据源抽取数据时遇到了问题,甚至是数据源缺失数据,往往就会产生这类数据。进一步来讲,计算负数的对数,执行计算或函数时抛出异常等特定情况,也可能产生这类数据。后续章节会讲解NaN值的几种不同处理方法。

    尽管NaN值是数据有问题才产生的,然而在pandas中是可以定义这种类型的数据,并把它添加到Series等数据结构中的。创建数据结构时,可为数组中元素缺失的项输入np.NaN

    >>> s2 = pd.Series([5,-3,np.NaN,14])
    >>> s2
    0    5
    1   -3
    2  NaN
    3   14

    isnull()notnull()函数用来识别没有对应元素的索引时非常好用。

    >>> s2.isnull( )
    0    False
    1    False
    2     True
    3    False
    dtype: bool
    >>> s2.notnull( )
    0     True
    1     True
    2    False
    3     True
    dtype: bool

    上述两个函数返回两个由布尔值组成的Series对象,其元素值是True还是False取决于原Series对象的元素是否为NaN。如果是NaNisnull()函数返回值为True;反之,如果不是NaNnotnull()函数返回值为True。这两个函数可用作筛选条件。

    >>> s2[s2.notnull( )]
    0     5
    1    -3
    3    14
    dtype: float64
    >>> s2[s2.isnull( )]
    2   NaN
    dtype: float64
  9. Series用作字典

    我们还可以把Series对象当作字典(dict,dictionary)对象来用。定义Series对象时,就可以利用这种相似性。事实上,我们可以用事先定义好的字典来创建Series对象。

    >>> mydict = {'red': 2000, 'blue': 1000, 'yellow': 500, 'orange': 1000}
    >>> myseries = pd.Series(mydict)
    blue      1000
    orange    1000
    red       2000
    yellow     500
    dtype: int64

    上述例子中,索引数组用字典的键来填充,每个索引所对应的元素为用作索引的键在字典中对应的值。你还可以单独指定索引,pandas会控制字典的键和数组索引标签之间的相关性。如遇缺失值处,pandas就会为其添加NaN

    >>> colors = ['red','yellow','orange','blue','green']
    >>> myseries = pd.Series(mydict, index=colors)
    red       2000
    yellow     500
    orange    1000
    blue      1000
    green      NaN
    dtype: float64
  10. Series对象之间的运算

    我们前面见识过Series对象和标量之间的数学运算,Series对象之间也可以进行这类运算,甚至标签也可以参与运算。

    事实上,Series这种数据结构在运算时有一大优点,它能够通过识别标签对齐不一致的数据。

    在下面这个例子中,我们来求只有部分元素标签相同的两个Series对象之和。

    >>> mydict2 = {'red':400,'yellow':1000,'black':700}
    >>> myseries2 = pd.Series(mydict2)
    >>> myseries + myseries2
    black      NaN
    blue       NaN
    orange     NaN
    green      NaN
    red       2400
    yellow    1500
    dtype: float64

    上述运算得到一个新Series对象,其中只对标签相同的元素求和。其他只属于任何一个Series对象的标签也被添加到新对象中,只不过它们的值均为NaN

4.5.2 DataFrame对象

DataFrame这种列表式数据结构跟工作表(最常见的是Excel工作表)极为相似,其设计初衷是将Series的使用场景由一维扩展到多维。DataFrame由按一定顺序排列的多列数据(见图4-2)组成,各列的数据类型可以有所不同(数值、字符串或布尔值等)。

图4-2 DataFrame数据结构

Series对象的Index数组存放有每个元素的标签,而DataFrame对象则有所不同,它有两个索引数组。第一个数组与行相关,它与Series的索引数组极为相似。每个标签与标签所在行的所有元素相关联。而第二个数组包含一系列列标签,每个标签与一列数据相关联。

DataFrame还可以理解为一个由Series组成的字典,其中每一列的名称为字典的键,形成DataFrame的列的Series作为字典的值。进一步来说,每个Series的所有元素映射到叫作Index的标签数组。

  1. 定义DataFrame对象

    新建DataFrame对象的最常用方法是传递一个dict对象给DataFrame()构造函数。dict对象以每一列的名称作为键,每个键都有一个数组作为值。

    >>> data = {'color' : ['blue','green','yellow','red','white'],
                         'object' : ['ball','pen','pencil','paper','mug'],
                         'price' : [1.2,1.0,0.6,0.9,1.7]}
    
    >>>frame = pd.DataFrame(data)
    >>> frame
        color object price
    0    blue   ball   1.2
    1   green    pen   1.0
    2 yellow  pencil   0.6
    3    red   paper   0.9
    4  white     mug   1.7

    如果用来创建DataFrame对象的dict对象包含一些用不到的数据,你可以只选择自己感兴趣的。在DataFrame构造函数中,用columns选项指定需要的列即可。新建的DataFrame各列顺序与你指定的列顺序一致,而与它们在字典中的顺序无关。

    >>> frame2 = pd.DataFrame(data, columns=['object','price'])
    >>> frame2
       object price
    0    ball   1.2
    1     pen   1.0
    2  pencil   0.6
    3   paper   0.9
    4     mug   1.7

    DataFrame对象跟Series一样,如果Index数组没有明确指定标签,pandas也会自动为其添加一列从0开始的数值作为索引。如果想用标签作为DataFrame的索引,则要把标签放到数组中,赋给index选项。

    >>> frame2 = pd.DataFrame(data, index=['one','two','three','four','five'])
    >>> frame2
            color object price
    one      blue   ball   1.2
    two     green    pen   1.0
    three  yellow pencil   0.6
    four      red  paper   0.9
    five    white    mug   1.7

    既已引入两个新选项indexcolumns,还可以想出一种定义DataFrame的新方法。我们不再使用dict对象,而是定义一个构造函数,指定三个参数,参数顺序如下:数据矩阵、index选项和columns选项。记得将存放有标签的数组赋给index选项,将存放有列名称的数组赋给columns选项。

    从书中后续很多例子中,你会看到,要方便、快捷地创建包含数据的数组,可以使用np.arange(16).reshape((4,4))生成一个4×4型、包含数字0~15的矩阵。

    >>> frame3 = pd.DataFrame(np.arange(16).reshape((4,4)),
    ...                   index=['red','blue','yellow','white'],
    ...                   columns=['ball','pen','pencil','paper'])
    >>> frame3
            ball pen pencil paper
    red        0   1      2     3
    blue       4   5      6     7
    yellow     8   9     10    11
    white     12   13    14    15
  2. 选取元素

    如想知道DataFrame对象所有列的名称,在它上面调用columns属性即可。

    >>> frame.columns
    Index([u'colors', u'object', u'price'], dtype='object')

    类似地,要获取索引列表,调用index属性即可。

    >>> frame.index
    Int64Index([0, 1, 2, 3, 4], dtype='int64')

    如果要获取存储在数据结构中的元素,可以使用values属性获取所有元素。

    >>> frame.values
    array([['blue', 'ball', 1.2],
           ['green', 'pen', 1.0],
           ['yellow', 'pencil', 0.6],
           ['red', 'paper', 0.9],
           ['white', 'mug', 1.7]], dtype=object)

    或者,如果想选择一列内容,把这一列的名称作为索引即可。

    >>> frame['price']
    0    1.2
    1    1.0
    2    0.6
    3    0.9
    4    1.7
    Name: price, dtype: float64

    如上所见,返回值为Series对象。另外一种方法是用列名称作为DataFrame实例的属性。

    >>> frame.price
    0    1.2
    1    1.0
    2    0.6
    3    0.9
    4    1.7
    Name: price, dtype: float64

    至于DataFrame中的行,用ix属性和行的索引值就能获取到。

    >>> frame.ix[2]
    color     yellow
    object    pencil
    price        0.6
    Name: 2, dtype: object

    返回结果同样是一个Series对象,其中列的名称已经变为索引数组的标签,而列中的元素变为Series的数据部分。

    用一个数组指定多个索引值就能选取多行:

    >>> frame.ix[[2,4]]
        color object price
    2 yellow  pencil   0.6
    4  white     mug   1.7

    若要从DataFrame抽取一部分,可以用索引值选择你想要的行。事实上,你可以把一行看作DataFrame的一部分,通过指定索引范围来选取,其中这一行的索引作为起始索引值(下面例子中的0),下一行的索引作为结束索引(下面例子中的1)。

    >>> frame[0:1]
      color object price
    0  blue    ball  1.2

    如上,返回结果为只包含一行数据的DataFrame对象。如需多行,必须扩展选择范围。

    >>> frame[1:3]
        color object price
    1   green    pen   1.0
    2 yellow  pencil   0.6

    最后,如要获取存储在DataFrame中的一个元素,需要依次指定元素所在的列名称、行的索引值或标签。

    >>> frame['object'][3]
    'paper'
  3. 赋值

    一旦你理解了组成DataFrame的各元素的获取方法,依照相同的逻辑就能增加或修改元素。

    例如,前面讲过用index属性指定DataFrame结构中的索引数组,用columns属性指定包含列名称的行。你还可以用name属性为这两个二级结构指定标签,便于识别。

    >>> frame.index.name = 'id'; frame.columns.name = 'item'
    >>> frame
    item  color object price
    id
    0      blue   ball   1.2
    1     green    pen   1.0
    2    yellow pencil   0.6
    3       red  paper   0.9
    4  white    mug   1.7

    灵活程度非常高是pandas数据结构的一大优点。事实上,你可以在任何层级修改它们的内部结构。例如,执行添加一列新元素这类常见的操作。

    添加列的方法很简单,指定DataFrame实例新列的名称,为其赋值即可。

    >>> frame['new'] = 12
    >>> frame
       colors object price new
    0    blue   ball   1.2  12
    1   green    pen   1.0  12
    2  yellow pencil   0.6  12
    3     red  paper   0.9  12
    4   white    mug   1.7  12

    从结果可以看出,DataFrame新增了名称为“new”的一列,它的各个元素均为12。

    然而,如果想更新一列的内容,需要把一个数组赋给这一列。

    >>> frame['new'] = [3.0,1.3,2.2,0.8,1.1]
    >>> frame
        color object price  new
    0    blue   ball   1.2  3.0
    1   green    pen   1.0  1.3
    2  yellow pencil   0.6  2.2
    3     red  paper   0.9  0.8
    4   white    mug   1.7  1.1

    如果想更新某一列的全部数据,方法类似。例如借助np.arange()函数预先定义一个序列,用它更新某一列的所有元素。

    为DataFrame的各列赋一个Series对象也可以创建DataFrame,例如使用np.arange()函数生成一个递增序列。

    >>> ser = pd.Series(np.arange(5))
    >>> ser
    0    0
    1    1
    2    2
    3    3
    4    4
    dtype: int32
    >>> frame['new'] = ser
    >>> frame
        color object price  new
    0    blue   ball   1.2    0
    1   green    pen   1.0    1
    2  yellow pencil   0.6    2
    3     red  paper   0.9    3
    4   white    mug   1.7    4

    最后,我们来看一下修改单个元素的方法:选择元素,为其赋新值即可。

    >>> frame['price'][2] = 3.3
  4. 元素的所属关系

    前面你已经见过用isin()函数判断一组元素是否属于Series对象,其实该函数对DataFrame对象也适用。

    >>> frame.isin([1.0,'pen'])
       color object  price
    0  False  False  False
    1  False   True   True
    2  False   False False
    3  False   False False
    4  False   False False

    你得到了一个只包含布尔值的DataFrame对象,其中只有满足从属关系之处元素为True。如把上述返回结果作为条件,将得到一个新DataFrame,其中只包含满足条件的元素。

    >>> frame[frame.isin([1.0,'pen'])]
      color object price
    0   NaN    NaN   NaN
    1   NaN    pen     1
    2   NaN    NaN   NaN
    3   NaN    NaN   NaN
    4   NaN    NaN   NaN
  5. 删除一列

    如想删除一整列的所有数据,使用del命令。

    >>> del frame['new']
    >>> frame
       colors object price
    0    blue   ball   1.2
    1   green    pen   1.0
    2  yellow pencil   0.6
    3     red  paper   0.9
    4   white    mug   1.7
  6. 筛选

    对于DataFrame对象,也可以通过指定条件筛选元素。例如,你想获取所有小于指定数字(比如12)的元素。

    >>> frame[frame < 12]
            ball  pen pencil paper
    red        0    1      2     3
    blue       4    5      6     7
    yellow     8    9     10    11
    white    NaN  NaN    NaN   NaN

    返回的DataFrame对象只包含所有小于12的数字,各元素的位置保持不变。其他不符合条件的元素被替换为NaN

  7. 用嵌套字典生成DataFrame对象

    嵌套字典是Python广泛使用的数据结构,示例如下:

    nestdict = { 'red': { 2012: 22, 2013: 33 },
                         'white': { 2011: 13, 2012: 22; 2013: 16},
                         'blue': {2011: 17, 2012: 27; 2013: 18}}

    直接将这种数据结构作为参数传递给DataFrame()构造函数,pandas就会将外部的键解释成列名称,将内部的键解释为用作索引的标签。

    解释嵌套结构时,可能并非所有位置都有相应的元素存在。pandas会用NaN填补缺失的元素。

    >>> nestdict = {'red':{2012: 22, 2013: 33},
    ...             'white':{2011: 13, 2012: 22, 2013: 16},
    ...             'blue': {2011: 17, 2012: 27, 2013: 18}}
    >>> frame2 = pd.DataFrame(nestdict)
    >>> frame2
          blue red white
    2011    17 NaN    13
    2012    27  22    22
    2013    18  33    16
  8. DataFrame转置

    处理列表数据时可能会用到转置操作(列变为行,行变为列)。pandas提供了一种很简单的转置方法。调用T属性就能得到DataFrame对象的转置形式。

    >>> frame2.T
           2011  2012  2013
    blue     17    27    18
    red     NaN    22    33
    white    13    22    16

4.5.3 Index对象

现在,你知道了Series、DataFrame对象以及它们的结构形式,对这些数据结构的特性也一定有所了解。事实上,它们在数据分析方面的大多数优秀特性都取决于完全整合到这些数据结构中的Index对象。

轴标签或其他用作轴名称的元数据就存储为Index对象。前面讲过如何把存储多个标签的数组转化为Index对象:指定构造函数的index选项。

>>> ser = pd.Series([5,0,3,8,4], index=['red','blue','yellow','white','green'])
>>> ser.index
Index([u'red', u'blue', u'yellow', u'white', u'green'], dtype='object')

跟pandas数据结构(Series和DataFrame)中其他元素不同的是,Index对象不可改变。声明后,它不能改变。不同数据结构共用Index对象时,该特性能够保证它的安全。

每个Index对象都有很多方法和属性,当你需要知道它们所包含的值时,这些方法和属性非常有用。

  1. Index对象的方法

    Index对象提供了几种方法,可用来获取数据结构索引的相关信息。例如,idmin()idmax()函数分别返回索引值最小和最大的元素。

    >>> ser.idxmin()
    'red'
    >>> ser.idxmax()
    'green'
  2. 含有重复标签的Index

    到目前为止,我们见过的所有索引都是位于一个单独的数据结构中,且所有标签都是唯一的。虽然只有满足这个条件,很多函数才能运行,但是对pandas数据结构而言,这个条件并不是必需的。

    我们来举一个例子,定义一个含有重复标签的Series。

    >>> serd = pd.Series(range(6), index=['white','white','blue','green','green','yellow'])
    >>> serd
    white     0
    white     1
    blue      2
    green     3
    green     4
    yellow    5
    dtype: int64

    从数据结构中选取元素时,如果一个标签对应多个元素,我们得到的将是一个Series对象而不是单个元素。

    >>> serd['white']
    white    0
    white    1
    dtype: int64

    以上逻辑适用于索引中存在重复项的DataFrame,其返回结果为DataFrame对象。

    数据结构很小时,识别索引的重复项很容易,但随着数据结构逐渐增大以后,难度也在增加。pandas的Index对象还有is_unique属性。调用该属性,就可以知道数据结构(Series和DataFrame)中是否存在重复的索引项。

    >>> serd.index.is_unique
    False
    >>> frame.index.is_unique
    True

4.6 索引对象的其他功能

与Python常用数据结构相比,pandas不仅利用了NumPy数组的高性能优势,还整合了索引机制。

最终事实证明,这样做颇有几分成效。事实上,虽然已有的动态数据结构极为灵活,但在结构中增加诸如标签这样的内部索引机制,为接下来及下一章要讲解的一系列必要操作,提供了更为简单、直接的执行方法。

这一节,我们来详细分析几种使用索引机制实现的基础操作。

  • 更换索引
  • 删除
  • 对齐

4.6.1 更换索引

前面讲过,数据结构一旦声明,Index对象就不能改变。这么说一点也没错,但是执行更换索引操作就可以解决这个问题。

事实上,重新定义索引之后,我们就能够用现有的数据结构生成一个新的数据结构。

>>> ser = pd.Series([2,5,7,4], index=['one','two','three','four'])
>>> ser
one      2
two      5
three    7
four     4
dtype: int64

pandas的reindex()函数可更换Series对象的索引。它根据新标签序列,重新调整原Series的元素,生成一个新的Series对象。

更换索引时,可以调整索引序列中各标签的顺序,删除或增加新标签。如增加新标签,pandas会添加NaN作为其元素。

>>> ser.reindex(['three','four','five','one'])
three     7
four      4
five    NaN
one       2
dtype: float64

从返回结果可以看到,标签顺序全部调整过。删除了标签two及其元素,增加了新标签five

然而,重新编制索引,定义所有的标签序列可能会很麻烦,对大型DataFrame来说更是如此。但是我们可以使用自动填充或插值方法。

为了更好地理解自动编制索引功能,我们先来定义以下Series对象。

>>> ser3 = pd.Series([1,5,6,3],index=[0,3,5,6])
>>> ser3
0    1
3    5
5    6
6    3
dtype: int64

刚定义的Series对象,其索引列并不完整而是缺失了几个值(124)。常见的需求为插值,以得到一个完整的序列。方法是用reindex()函数,method选项的值为ffill。此外,还需要指定索引值的范围。要指定一列0~5的值,参数为range(6)

>>> ser3.reindex(range(6),method='ffill')
0    1
1    1
2    1
3    5
4    5
5    6
dtype: int64

由结果可见,新Series对象添加了原Series对象缺失的索引项。新插入的索引项,其元素为前面索引编号比它小的那一项的元素。所以我们看到索引项1、2的值为1,也就是索引项0的值。

如果你想用新插入索引后面的元素,需要使用bfill方法。

>>> ser3.reindex(range(6),method='bfill')
0    1
1    5
2    5
3    5
4    6
5    6
dtype: int64

用这种方法,索引项1和2的元素则为5,也就是索引项3的元素。

更换索引的概念可以由Series扩展到DataFrame,你不仅可以更换索引(行),还可以更换列,甚至更换两者。如前所述,我们可以增加行或列,但pandas用NaN弥补原数据结构中缺失的元素。

>>> frame.reindex(range(5), method='ffill',columns=['colors','price','new','object'])
   colors price  new  object
0    blue   1.2  NaN    ballpand
1   green   1.0  NaN     pen
2  yellow   0.6  NaN  pencil
3     red   0.9  NaN   paper
4   white   1.7  NaN     mug

4.6.2 删除

另外一种跟Index对象相关的操作是删除。因为索引和列名称有了标签作为标识,所以删除操作变得很简单。

pandas专门提供了一个用于删除操作的函数:drop(),它返回不包含已删除索引及其元素的新对象。

举例来说,我们想从Series对象中删除一项。为此,我们先来定义一个含有四个元素的Series对象,其中各元素标签均不相同。

>>> ser = Series(np.arange(4.), index=['red','blue','yellow','white'])
>>> ser
red       0
blue      1
yellow    2
white     3
dtype: float64

假如我们想删除标签为yellow的这一项。用标签作为drop()函数的参数,就可以删除这一项。

>>> ser.drop('yellow')
red      0
blue     1
white    3
dtype: float64

传入一个由多个标签组成的数组,可以删除多项。

>>> ser.drop(['blue','white'])
red       0
yellow    2
dtype: float64

要删除DataFrame中的元素,需要指定元素两个轴的轴标签。我们通过具体的例子来看一下,首先声明一个DataFrame对象。

>>> frame = pd.DataFrame(np.arange(16).reshape((4,4)),
...                   index=['red','blue','yellow','white'],
...                   columns=['ball','pen','pencil','paper'])
>>> frame
        ball  pen  pencil  paper
red        0    1       2      3
blue       4    5       6      7
yellow     8    9      10     11
white     12   13      14     15

传入行的索引可删除行。

>>> frame.drop(['blue','yellow'])
       ball  pen  pencil paper
red       0    1       2     3
white    12   13      14    15

要删除列,需要指定列的索引,但是还必须用axis选项指定从哪个轴删除元素。如按照列的方向删除,axis的值为1

>>> frame.drop(['pen','pencil'],axis=1)
        Ball  paper
red        0      3
blue       4      7
yellow     8     11
white     12     15

4.6.3 算术和数据对齐

pandas能够将两个数据结构的索引对齐,这可能是与pandas数据结构索引对象有关的最强大的功能。这一点尤其体现在数据结构之间的算术运算上。参与运算的两个数据结构,其索引项顺序可能不一致,而且有的索引项可能只存在于一个数据结构中。

从下面几个例子中,你就会发现做算术运算时,pandas很擅长对齐不同数据结构的索引项。我们来举个例子,先来定义两个Series对象,分别指定两个不完全一致的标签数组。

>>> s1 = pd.Series([3,2,5,1],['white','yellow','green','blue'])
>>> s2 = pd.Series([1,4,7,2,1],['white','yellow','black','blue','brown'])

算术运算种类很多,我们考虑一下最简单的求和运算。刚定义的两个Series对象,有些标签两者都有,有些只属于其中一个对象。如果一个标签,两个Series对象都有,就把它们的元素相加,反之,标签也会显示在结果(新Series对象)中,只不过元素为NaN

>>> s1 + s2
black    NaN
blue       3
brown    NaN
green    NaN
white      4
yellow     6
dtype: float64

DataFrame对象之间的运算,虽然看起来可能更复杂,但对齐规则相同,只不过行和列都要执行对齐操作。

>>> frame1 = pd.DataFrame(np.arange(16).reshape((4,4)),
...                   index=['red','blue','yellow','white'],
...                   columns=['ball','pen','pencil','paper'])
>>> frame2 = pd.DataFrame(np.arange(12).reshape((4,3)),
...                   index=['blue','green','white','yellow'],
...                   columns=['mug','pen','ball'])
>>> frame1
        ball  pen  pencil  paper
red        0    1       2      3
blue       4    5       6      7
yellow     8    9      10     11
white     12   13      14     15
>>> frame2
        mug  pen  ball
blue      0    1     2
green     3    4     5
white     6    7     8
yellow    9   10    11
>>> frame1 + frame2
        ball  mug  paper pen pencil
blue       6  NaN    NaN   6    NaN
green    NaN  NaN    NaN NaN    NaN
red      NaN  NaN    NaN NaN    NaN
white     20  NaN    NaN  20    NaN
yellow    19  NaN    NaN  19    NaN

4.7 数据结构之间的运算

既然你已经熟悉了Series和DataFrame等数据结构,以及针对它们的多种基础操作,我们接下来讲解两种及以上数据结构之间的运算。

例如,上一节我们曾学过两个对象之间的算术运算。这一节,我们则对两种数据结构之间的运算进行深入探讨。

4.7.1 灵活的算术运算方法

前面刚学过可直接在pandas数据结构之间使用算术运算符。相同的运算还可以借助灵活的算术运算方法(flexible arithmetic methods)来完成。

  • add()
  • sub()
  • div()
  • mul()

这些函数的调用方法与数学运算符的使用方法不同。例如,两个DataFrame对象的求和运算,不再使用“frame1+frame2”这种格式,而是使用下面这种格式:

>>> frame1.add(frame2)
        ball  mug  paper  pen pencil
blue       6  NaN    NaN    6    NaN
green    NaN  NaN    NaN  NaN    NaN
red      NaN  NaN    NaN  NaN    NaN
white     20  NaN    NaN   20    NaN
yellow    19  NaN    NaN   19    NaN

如上所见,结果跟使用+运算符所得到的相同。你可能还注意到,如果两个DataFrame对象的索引和列名称差别很大,新得到的DataFrame对象将有很多元素为NaN。本章后面会讲到这类数据的处理方法。

4.7.2 DataFrame和Series对象之间的运算

再次回到算术运算符,pandas允许参与运算的对象为不同的数据结构,比如DataFrame和Series。举例之前,先来定义两个不同的数据结构。

>>> frame = pd.DataFrame(np.arange(16).reshape((4,4)),
...                   index=['red','blue','yellow','white'],
...                   columns=['ball','pen','pencil','paper'])
>>> frame
        ball  pen pencil paper
red        0    1      2     3
blue       4    5      6     7
yellow     8    9     10    11
white     12   13     14    15
>>> ser = pd.Series(np.arange(4), index=['ball','pen','pencil','paper'])
>>> ser
ball      0
pen       1
pencil    2
paper     3
dtype: int32

定义数据结构时,我们特意让Series对象的索引和DataFrame对象的列名称保持一致。这样就可以直接对它们进行运算。

>>> frame - ser
        ball  pen pencil paper
red        0    0      0     0
blue       4    4      4     4
yellow     8    8      8     8
white     12   12     12    12

如上所见,DataFrame对象各元素分别减去了Series对象中索引与之相同的元素。DataFrame对象每一列的所有元素,无论对应哪一个索引项,都执行了减法操作。

如果一个索引项只存在于其中一个数据结构之中,则运算结果中会为该索引项生成一列,只不过该列的所有元素都是NaN

>>> ser['mug'] = 9
>>> ser
ball      0
pen       1
pencil    2
paper     3
mug       9
dtype: int64
>>> frame - ser
        ball  mug paper pen pencil
red        0  NaN     0   0      0
blue       4  NaN     4   4      4
yellow     8  NaN     8   8      8
white     12  NaN    12  12     12

4.8 函数应用和映射

这一节将讲解pandas库函数。

4.8.1 操作元素的函数

pandas库以NumPy为基础,并对它的很多功能进行了扩展,以用来操作新数据结构Series和DataFrame。通用函数ufunc,universal function)就是经过扩展得到的功能,这类函数能够对数据结构中的元素进行操作,因此特别有用。

>>> frame = pd.DataFrame(np.arange(16).reshape((4,4)),
...                   index=['red','blue','yellow','white'],
...                   columns=['ball','pen','pencil','paper'])
>>> frame
        ball  pen  pencil  paper
red        0    1       2      3
blue       4    5       6      7
yellow     8    9      10     11
white     12   13      14     15

例如,使用NumPy的np.sqrt()函数就能计算DataFrame对象每个元素的平方根。

>>> np.sqrt(frame)
            ball       pen    pencil     paper
red     0.000000  1.000000  1.414214  1.732051
blue    2.000000  2.236068  2.449490  2.645751
yellow  2.828427  3.000000  3.162278  3.316625
white   3.464102  3.605551  3.741657  3.872983

4.8.2 按行或列执行操作的函数

除了通用函数,用户还可以自己定义函数。需要注意的是这些函数对一维数组进行运算,返回结果为一个数值。例如,我们可以定义一个计算数组元素取值范围的lambda函数。

>>> f = lambda x: x.max() - x.min()

还可以用下面这种形式定义函数:

>>> def f(x):
...    return x.max() - x.min()
...

apply()函数可以在DataFrame对象上调用刚定义的函数。

>>> frame.apply(f)
ball      12
pen       12
pencil    12
paper     12
dtype: int64

然而,每一列的运算结果为一个数值。如果你想用函数处理行而不是列,需将axis选项设置为1

>>> frame.apply(f, axis=1)
red       3
blue      3
yellow    3
white     3
dtype: int64

apply()函数并不是一定要返回一个标量,它还可以返回Series对象,因而可以借助它同时执行多个函数。每调用一次函数,就会有两个或两个以上的返回结果。我们可以像下面这样指定一个函数。

>>> def f(x):
...     return pd.Series([x.min(), x.max()], index=['min','max'])
...

像之前一样,应用这个函数,但是返回结果不再是Series而是DataFrame对象,并且DataFrame对象的行数跟函数返回值的数量相等。

>>> frame.apply(f)
     ball  pen  pencil  paper
min     0    1       2      3
max    12   13      14     15

4.8.3 统计函数

数组的大多数统计函数对DataFrame对象依旧有效,因此没有必要使用apply()函数。例如,sum()mean()函数分别用来计算DataFrame对象元素之和及它们的均值。

>>> frame.sum()
ball      24
pen       28
pencil    32
paper     36
dtype: int64
>>> frame.mean()
ball      6
pen       7
pencil    8
paper     9
dtype: float64

describe()函数能够计算多个统计量。

>>> frame.describe()
            ball       pen      pencil      paper
count   4.000000  4.000000    4.000000   4.000000
mean    6.000000  7.000000    8.000000   9.000000
std     5.163978  5.163978    5.163978   5.163978
min     0.000000  1.000000    2.000000   3.000000
25%     3.000000  4.000000    5.000000   6.000000
50%     6.000000  7.000000    8.000000   9.000000
75%     9.000000  10.000000  11.000000  12.000000
max    12.000000  13.000000  14.000000  15.000000

4.9 排序和排位次

另外一种使用索引机制的基础操作是排序(sorting)。对数据进行排序通常为必要操作,因此简化它的实现非常重要。pandas的sort_index()函数返回一个跟原对象元素相同但顺序不同的新对象。

首先看一下Series对象各项的排序方法。要排序的索引只有一列,因此操作很简单。

>>> ser = pd.Series([5,0,3,8,4], index=['red','blue','yellow','white','green'])
>>> ser
red       5
blue      0
yellow    3
white     8
green     4
dtype: int64
>>> ser.sort_index()
blue      0
green     4
red       5
white     8
yellow    3
dtype: int64

输出结果中,各元素按照以字母表顺序升序排列(A~Z)的标签进行排序。这是默认的排序方法,但若指定ascending选项,将其值置为False,则可按照降序排列。

>>> ser.sort_index(ascending=False)
yellow    3
white     8
red       5
green     4
blue      0
dtype: int64

对于DataFrame对象,可分别对两条轴中的任意一条进行排序。如果要根据索引对行进行排序,可依旧使用sort_index()函数,不用指定参数,前面已经讲过;如果要按列进行排序,则需要指定axis选项,其值为1

>>> frame = pd.DataFrame(np.arange(16).reshape((4,4)),
...                   index=['red','blue','yellow','white'],
...                   columns=['ball','pen','pencil','paper'])
>>> frame
        ball  pen  pencil  paper
red        0    1       2      3
blue       4    5       6      7
yellow     8    9      10     11
white     12   13      14     15
>>> frame.sort_index()
        ball  pen  pencil  paper
blue       4    5       6      7
red        0    1       2      3
white     12   13      14     15
yellow     8    9      10     11
>>> frame.sort_index(axis=1)
        ball  paper  pen  pencil
red        0      3    1       2
blue       4      7    5       6
yellow     8     11    9      10
white     12     15   13      14

至此,我们已经讲解了根据索引进行排序的方法。但你往往还需要对数据结构中的元素进行排序,对于这个问题,Series和DataFrame对象有所不同,要区别对待。

对Series对象排序,使用order()函数。

>>> ser.order()
blue      0
yellow    3
green     4
red       5
white     8
dtype: int64

对DataFrame对象排序,使用前面用过的sort_index()函数,只不过要用by选项指定根据哪一列进行排序。

>>> frame.sort_index(by='pen')
        ball  pen  pencil  paper
red        0    1       2      3
blue       4    5       6      7
yellow     8    9      10     11
white     12   13      14     15

如果要基于两列或更多的列进行排序,则把这些列的名称放到数组中,赋给by选项。

>>> frame.sort_index(by=['pen','pencil'])
        ball  pen  pencil  paper
red        0    1       2      3
blue       4    5       6      7
yellow     8    9      10     11
white     12   13      14     15

排位次操作(ranking)跟排序操作紧密相关,该操作为序列的每个元素安排一个位次(初始值为1,依次加1),位次越靠前,所使用的数值越小。

>>> ser.rank()
red       4
blue      1
yellow    2
white     5
green     3
dtype: float64

我们还可以把数据在数据结构中的顺序(没有进行排序操作)作为它的位次。只要使用method选项把first赋给它即可。

>>> ser.rank(method='first')
red       4
blue      1
yellow    2
white     5
green     3
dtype: float64

默认位次使用升序。要按照降序排列,就把ascending选项的值置为False

>>> ser.rank(ascending=False)
red       2
blue      5
yellow    4
white     1
green     3
dtype: float64

4.10 相关性和协方差

相关性(correlation)和协方差(covariance)是两个重要的统计量,pandas计算这两个量的函数分别是corr()cov()。这两个量的计算通常涉及两个Series对象。

>>> seq2 = pd.Seri
es([3,4,3,4,5,4,3,2],['2006','2007','2008','2009','2010','2011','2012','2013'])
>>> seq = pd.Seri
es([1,2,3,4,4,3,2,1],['2006','2007','2008','2009','2010','2011','2012','2013'])
>>> seq.corr(seq2)
0.77459666924148329
>>> seq.cov(seq2)
0.8571428571428571

另外一种情况是,计算单个DataFrame对象的相关性和协方差,返回两个新DataFrame对象形式的矩阵。

>>> frame2 = DataFrame([[1,4,3,6],[4,5,6,1],[3,3,1,5],[4,1,6,4]],
...                      index=['red','blue','yellow','white'],
...                      columns=['ball','pen','pencil','paper'])
>>> frame2
        ball  pen  pencil  paper
red        1    4       3      6
blue       4    5       6      1
yellow     3    3       1      5
white      4    1       6      4

>>> frame2.corr()
            ball        pen     pencil      paper
ball    1.000000  -0.276026   0.577350  -0.763763
pen    -0.276026   1.000000  -0.079682  -0.361403
pencil  0.577350  -0.079682   1.000000  -0.692935
paper  -0.763763  -0.361403  -0.692935   1.000000
>>> frame2.cov()
            ball        pen     pencil      paper
ball    2.000000  -0.666667   2.000000  -2.333333
pen    -0.666667   2.916667  -0.333333  -1.333333
pencil  2.000000  -0.333333   6.000000  -3.666667
paper  -2.333333  -1.333333  -3.666667   4.666667

corrwith()方法可以计算DataFrame对象的列或行与Series对象或其他DataFrame对象元素两两之间的相关性。

>>> serred       0
blue     1
yellow   2
white    3
green    9
dtype: float64
>>> frame2.corrwith(ser)
ball      0.730297
pen      -0.831522
pencil    0.210819
paper    -0.119523
dtype: float64
>>> frame2.corrwith(frame)
ball      0.730297
pen      -0.831522
pencil    0.210819
paper    -0.119523
dtype: float64

4.11 NaN数据

由前几节可知,补上缺失的数值很容易,它们在数据结构中用NaN来表示,以便于识别。在数据分析过程中,有些元素在某个数据结构中没有定义,这种情况很常见。

pandas意在更好地管理这种可能出现的情况。事实上,这一节我们将讲解缺失值的处理方法,这样许多问题就可以避免。比如,pandas库在计算各种描述性统计量时,其实没有考虑NaN值。

4.11.1 为元素赋NaN值

有时需要为数据结构中的元素赋NaN值,这时用NumPy的np.NaN(或np.nan)即可。

>>> ser = pd.Series([0,1,2,np.NaN,9], index=['red','blue','yellow','white','green'])
>>> ser
red        0
blue       1
yellow     2
white    NaN
green      9
dtype: float64
>>> ser['white'] = None
>>> ser
red        0
blue       1
yellow     2
white    NaN
green      9
dtype: float64

4.11.2 过滤NaN

数据分析过程中,有几种去除NaN的方法。然而,若要人工逐一删除NaN元素很麻烦,也很不安全,因为无法确保删除了所有的NaN。而dropna()函数可以帮你解决这个问题。

>>> ser.dropna()
red       0
blue      1
yellow    2
green     9
dtype: float64

另外一种方法是,用notnull()函数作为选取元素的条件,实现直接过滤。

>>> ser[ser.notnull()]
red       0
blue      1
yellow    2
green     9
dtype: float64

DataFrame处理起来要稍微复杂点。如果对这类对象使用dropna()方法,只要行或列有一个NaN元素,该行或列的全部元素都会被删除。

>>> frame3 = pd.DataFrame([[6,np.nan,6],[np.nan,np.nan,np.nan],[2,np.nan,5]],
...                        index = ['blue','green','red'],
...                        columns = ['ball','mug','pen'])

>>> frame3
       ball  mug  pen
blue      6  NaN    6
green   NaN  NaN  NaN
red       2  NaN    5
>>> frame3.dropna()
Empty DataFrame
Columns: [ball, mug, pen]
Index: []

因此,为了避免删除整行或整列,需使用how选项,指定其值为all,告知dropna()函数只删除所有元素均为NaN的行或列。

>>> frame3.dropna(how='all')
      ball  mug  pen
blue     6 NaN     6
red      2 NaN     5

4.11.3 为NaN元素填充其他值

删除NaN元素,可能会删除跟数据分析相关的其他数据,所以与其冒着风险去过滤NaN元素,不如用其他数值替代NaNfillna()函数能够满足大多数需要。这个函数以替换NaN的元素作为参数。所有NaN可以替换为同一个元素,如下所示:

>>> frame3.fillna(0)
       ball  mug  pen
blue      6    0    6
green     0    0    0
red       2    0    5

或者,若要将不同列的NaN替换为不同的元素,依次指定列名称及要替换成的元素即可。

>>> frame3.fillna({'ball':1,'mug':0,'pen':99})
       ball  mug  pen
blue      6    0    6
green     1    0   99
red       2    0    5

4.12 等级索引和分级

等级索引(hierarchical indexing)是pandas的一个重要功能,单条轴可以有多级索引。你可以像操作两维结构那样处理多维数据。

举个简单的例子:创建包含两列索引的Series对象,也就是说,创建一个包含两层的数据结构。

>>> mser = pd.Series(np.random.rand(8),
...        index=[['white','white','white','blue','blue','red','red','red'],
...        ['up','down','right','up','down','up','down','left']])
>>> mser
white  up       0.461689
       down     0.643121
       right    0.956163
blue   up       0.728021
       down     0.813079
red    up       0.536433
       down     0.606161
       left     0.996686
dtype: float64

>>> mser.index
MultiIndex(levels=[[u'blue', u'red', u'white'], [u'down', u'left', u'right', u'up']],
           labels=[[2, 2, 2, 0, 0, 1, 1, 1], [3, 0, 2, 3, 0, 3, 0, 1]])ù

通过指定等级索引,二级元素的选取操作得以简化。

事实上,你可以选取第一列索引中某一索引项的元素,使用最经典的做法即可:

>>> mser['white']
up       0.461689
down     0.643121
right    0.956163
dtype: float64

或者像下面这样,选取第二列索引中某一索引项的元素:

>>> mser[:,'up']
white    0.461689
blue     0.728021
red      0.536433
dtype: float64

若要选取某一特定的元素,指定两个索引即可,很直观吧?

>>> mser['white','up']
0.46168915430531676

等级索引在调整数据形状和进行基于组的操作(比如创建数据透视表)方面起着非常重要的作用。例如,可以使用unstack()函数调整DataFrame中的数据。这个函数把使用的等级索引Series对象转换为一个简单的DataFrame对象,其中把第二列索引转换为相应的列。

>>> mser.unstack()
           down      left     right        up
blue   0.813079       NaN       NaN  0.728021
red    0.606161  0.996686       NaN  0.536433
white  0.643121       NaN  0.956163  0.461689

如果想进行逆操作,把DataFrame对象转换为Series对象,可使用stack()函数。

>>> frame
        ball  pen  pencil  paper
red        0    1       2      3
blue       4    5       6      7
yellow     8    9      10     11
white     12   13      14     15
>>> frame.stack()
red     ball       0
        pen        1
        pencil     2
        paper      3
blue    ball       4
        pen        5
        pencil     6
        paper      7
yellow  ball       8
        pen        9
        pencil    10
        paper     11
white   ball      12
        pen       13
        pencil    14
        paper     15
dtype: int32

对于DataFrame对象,可以为它的行和列都定义等级索引。声明DataFrame对象时,为index选项和columns选项分别指定一个元素为数组的数组。

>>> mframe = pd.DataFrame(np.random.randn(16).reshape(4,4),
...      index=[['white','white','red','red'], ['up','down','up','down']],
...      columns=[['pen','pen','paper','paper'],[1,2,1,2]])
>>> mframe
                 pen               paper
                   1         2         1         2
white up   -1.964055  1.312100 -0.914750 -0.941930
      down -1.886825  1.700858 -1.060846 -0.197669
red   up   -1.561761  1.225509 -0.244772  0.345843
      down  2.668155  0.528971 -1.633708  0.921735

4.12.1 重新调整顺序和为层级排序

有时,你需要调整某一条轴上各层级的顺序或者调整某一层中各元素的顺序。

swaplevel()函数以要互换位置的两个层级的名称为参数,返回交换位置后的一个新对象,其中各元素的顺序保持不变。

>>> mframe.columns.names = ['objects','id']
>>> mframe.index.names = ['colors','status']
>>> mframe
objects             pen               paper
id                    1         2         1        2
colors status
white  up     -1.964055  1.312100 -0.914750 -0.941930
       down   -1.886825  1.700858 -1.060846 -0.197669
red    up     -1.561761  1.225509 -0.244772  0.345843
       down    2.668155  0.528971 -1.633708  0.921735

>>> mframe.swaplevel('colors','status')
objects             pen               paper
id                    1         2         1         2
status colors
up     white  -1.964055  1.312100 -0.914750 -0.941930
down   white  -1.886825  1.700858 -1.060846 -0.197669
up     red    -1.561761  1.225509 -0.244772  0.345843
down   red     2.668155  0.528971 -1.633708  0.921735

sortlevel()函数只根据一个层级对数据排序。

>>> mframe.sortlevel('colors')
objects             pen              paper
id                    1        2         1         2
colors status
red    down   2.668155  0.528971 -1.633708  0.921735
       up    -1.561761  1.225509 -0.244772  0.345843
white  down  -1.886825  1.700858 -1.060846 -0.197669
       up    -1.964055  1.312100 -0.914750 -0.941930

4.12.2 按层级统计数据

DataFrame或Series对象的很多描述性和概括统计量都有level选项,可用它指定要获取哪个层级的描述性和概括统计量。

例如,你想对行一层级进行统计,把层级的名称赋给level选项即可。

>>> mframe.sum(level='colors')
objects       pen                paper
id              1         2          1         2
colors
red      1.106394  1.754480  -1.878480  1.267578
white   -3.850881  3.012959  -1.975596 -1.139599

若想对某一层级的列进行统计,例如id,则需要把axis选项的值设置为1,把第二条轴作为参数。

>>> mframe.sum(level='id', axis=1)
id                    1         2
colors status
white  up     -2.878806  0.370170
       down   -2.947672  1.503189
red    up     -1.806532  1.571352
       down    1.034447  1.450706

4.13 小结

本章介绍了pandas库,你从中学到了它的安装方法,并且对它的特点有了全面的认识。

想必你对它的两种基础数据结构Series和DataFrame,以及它们的操作方法和主要特点,也有了较为详细的了解。尤其是,你已经知道了这两种结构索引机制的重要性以及它们的最佳操作方法。最后,你还学习了通过创建层级索引扩展这两种数据结构,以按照不同的次级层级分别处理其中的数据。

下一章将学习从文件等外部数据源获取数据,以及把分析结果写入文件的方法。

目录

  • 版权声明
  • 致谢
  • 译者序
  • 第 1 章 数据分析简介
  • 第 2 章 Python世界简介
  • 第 3 章 NumPy库
  • 第 4 章 pandas库简介
  • 第 5 章 pandas:数据读写
  • 第 6 章 深入pandas:数据处理
  • 第 7 章 用matplotlib实现数据可视化
  • 第 8 章 用scikit-learn库实现机器学习
  • 第 9 章 数据分析实例——气象数据
  • 第 10 章 IPython Notebook内嵌JavaScript库D3
  • 第 11 章 识别手写体数字
  • 附录 A 用LaTeX编写数学表达式
  • 附录 B 开放数据源