Pandas中的对象简介
Python 数据科学手册(抢先版)

从本质上来说,Pandas中的对象可以认为是NumPy中的结构化数组的一个增强版本,其中的行和列是以标签来标识的,而不是简单的整数索引。正如我们将在本章的余下内容中所看到的,Pandas在这些数据结构之上提供了许多有用的工具、方法和功能,但是几乎所有这些都建立在对于这些数据机构的理解之上。第一节中将介绍三种基本的Pandas数据结构:Series、DataFrame和Index。

正如导入numpy的标准别名是np一样,导入pandas的标准别名是pd

import numpy as np
import pandas as pd

Pandas Series

Series是一个包含被索引的数据的一维的数组。它可以由如下的列表或者数组创建而成:

data = pd.Series([0.25, 0.5, 0.75, 1.0])
data

从上面的输出我们可以看到,Series包含一组values 序列,以及一组index 序列,我们可以通过values 属性和index 属性来访问。其中,values 是我们所熟悉的NumPy数组:

data.values

而index 是一个类似数组的pd.Index类型对象,我们将在后面讨论其细节。

data.index

和NumPy数组相似,我们可以用Python的方括号表示法(在方括号中输入数据所关联的索引)来访问数据。

data[1]
data[1:3]

下面我们将看到,Pandas Series比它所模拟的一维NumPy数据更加通用和灵活。

将Series视为泛化的NumPy数组

从我们目前所看到的,似乎Series对象基本上跟一个一维NumPy数组是可互相替换的。但其根本的不同在于索引的出现:Numpy数组有一组隐式定义的整型索引用来访问数据,而Pandas Series有一组显式定义的关联到数据值的索引。

这一显式索引为Series对象提供了额外的能力。例如,索引值不一定需要是一个整数,而是可以由任何所需要的类型的值来组成。例如,如果我们愿意的话,我们可以用字符串来作为一个索引。

data = pd.Series([0.25, 0.5, 0.75, 1.0],
                       index=['a', 'b', 'c', 'd'])
data

这里数据访问的结果会如你所想的那样:

data['b']

我们甚至可以使用不连续的或者无序的索引

data = pd.Series([0.25, 0.5, 0.75, 1.0],
                                      index=[2, 5, 3, 7])
data
data[5]

将Series视为特殊化的字典

以这种方式,你可以将Pandas Series理解为一个特殊化的Python字典。字典是一种将特定的键映射到特定的值的数据结构,而Series是将一种类型的键映射到一组某种类型的值上的数据结构。这一类型化是重要的,正如NumPy数据背后基于特定类型编译后的代码使得它比Python列表在某些操作上更为高效,Pandas Series中的类型信息使得它比Python字典在某些操作上更为高效。

通过直接从一个Python字典来创建一个Series对象,可以使得Series与字典类比更为清晰。

population_dict = {'California': 38332521,
               'Texas': 26448193,
               'New York': 19651127,
               'Florida': 19552860,
               'Illinois': 12882135}
population = pd.Series(population_dict)
population

默认情况下,Series将字典的键排序后作为索引来建立。这里,典型的字典型项可以这样访问:

population['California']

与字典不同的是,Series也支持数组式的操作,比如切片。

population['California':'Illinois']

我们将在第X.X节讨论一些Pandas中索引和切片的趣事。

构造Series对象

我们已经从一些例子中了解了一些构造Pandas Series的方法。它们都是下面这个的一种版本,

>>> pd.Series(data, index=index)

其中index是可选的参数,而data可以是许多实体中的一个。

例如,data可以是一个列表或者NumPy数组,其中index默认是一个整数序列

pd.Series([2, 4, 6])

data也可以是一个标量,该标量将会被用以填充所有给定的Index:

pd.Series(5, index=[100, 200, 300])

data可以是一个字典,其中默认的index是排序后的字典键:

pd.Series({2:'a', 1:'b', 3:'c'})

每一种情况中,如果你想要不同的结果,index都是可以显式设定的:

pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2])

请注意,这里我们明确指定了将要从所给的字典中包含到Series中的索引值。

Pandas DataFrame

下一个我们要介绍的Pandas基本数据结构是DataFram。与上面提到的Series类似,DataFrame也可以想象成一个NumPy数组的泛化或者是特殊化的Python字典。我们接下来将要讨论这些看法。

将DataFrame视为一个泛化的NumPy数组

如果一个Series像是一个带有灵活索引的一维数组,那么一个DataFrame就像是一个带有灵活的行索引以及灵活的列名的二维数组。正如你可能认为一个二维数组是一个对齐的一维列的有序序列,你可以认为一个DataFrame是一个对齐的Series对象序列。这里,“对齐”我们指的是他们共享相同的索引。

为了展示这一概念,我们先来构造一个新的Series,将之前提到过的五个州的面积列出来。

area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297, 'Florida': 170312, 'Illinois': 149995}
area = pd.Series(area_dict)
area

现在我们有了这个Series加上之前构造的Series对象“population” ,我们可以用一个字典来构造一个包含这些信息的二维对象:

states = pd.DataFrame({'population': population,
     'area': area})
states

和Series对象类似,DataFrame也有一个index属性,使得我们可以访问到所有的index标签:

states.index

此外,DataFrame还有一个columns属性,这是一个包含了列标签的Index对象

states.columns

由此,DataFrame可以被认为是一个二维NumPy数组的泛化,其中行和列都有泛化的索引可以访问数据。

将DataFrame看做是一个特殊的字典

类似的,我们可以将一个DataFrame看做是一个特殊化的字典。字典将一个键映射到一个值,而DataFrame将一个列的名子映射到一个Series对象的列数据。例如,请求“area”属性将返回我们上面所看到的包含面积的Series对象。

states['area']

这里有一点可能容易令人困惑:在一个二维的NumPy数组中,data[0]将返回第一行。而对于一个DataFrame来说,data[‘col0’]将会返回第一列。正因为如此,我们最好将DataFrame理解为泛化的字典而不是泛化的数组,尽管这两种看法都是可行的。我们将在第X.X节中探索更加灵活的索引DataFrames的方法。

构造DataFrame对象

一个Pandas DataFrame可以通过不同的方法来构造,这里我们将给出几个例子:

从单个Series对象构造

一个DataFrame是一个Series对象的集合,并且一个单列的DataFrame可以由单个Series对象来构造:

pd.DataFrame(population, columns=['population'])

从一个字典列表来构造

任何字典列表都可以构造一个DataFrame。我们将用一个简单的列表来创建一些数据并构造出DataFrame:

data = [{'a': i, 'b': 2 * i}
for i in range(3)]
pd.DataFrame(data)

即使字典中的一些键是缺失的,Pandas也会用NaN(也即“not a number”:不是一个数字)来进行填充:

pd.DataFrame([{'a': 1, 'b': 2}, {'b': 3, 'c': 4}])

从一个包含Series对象的字典来构造

这一方法我们在之前已经看到过了,一个DataFrame也可以从一个Series对象的字典来构造。

pd.DataFrame({'population': population,
'area': area})

从一个二维NumPy数组来构造

给定一个二维数组的数据,我们可以创建一个包含任意指定列和索引名的DataFrame。如果,不指定的话,每列将是使用整型下标索引。

pd.DataFrame(np.random.rand(3, 2),
columns=['foo', 'bar'],
index=['a', 'b', 'c'])

从一个NumPy的结构化数组构造

我们在第X.X节中讲过结构化数组。一个Pandas DataFrame操作起来非常类似一个结构化数据,并且可以直接通过一个结构化数组来构造。

A = np.zeros(3, dtype=[('A', 'i8'), ('B', 'f8')])
A
pd.DataFrame(A)

Pandas Index

上面我们看到了Series和DataFrame都包含了显式的Index使得你可以引用和修改数据。这个Index对象本身是一个有趣的结构,并且它可以被看做是一个不变的数据或者是一个有序的不重复集合。这两种看法对Index对象的可用操作有着很有趣的影响。作为一个简单的例子,让我们从一个整数列表来构造一个Index:

ind = pd.Index([2, 3, 5, 7, 11])
ind

将Index看做不可变数组

Index中很多的操作与数组类似。例如,我们可以用标准的Python下标表示法来获取值或者切片:

ind[1]
ind[::2]

Index对象也有许多在NumPy数组中我们熟知的属性:

print(ind.size, ind.shape, ind.ndim, ind.dtype)

Index对象和NumPy数组的一个不同点是,Index是不可变的,也即通过一般的途径是不能修改的。

ind[1] = 0

这种不可变性使得在多个DataFrame和数组之间共享Index更为安全,将不会有无意的Index修改所造成的潜在副作用。

将Index看做一个有序的集合

Pandas对象被设计成便于如数据集之间的连接等操作,这些操作都依赖于集合运算的许多方面。回想一下,我们在第X.X节中详细介绍过,Python有一个内置的集合对象。Index对象遵循了很多这一内置集合对象的习惯用法,使得并、交、差以及其他组合运算能以熟悉的方法来计算:

indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])
indA & indB  # intersection
indA | indB  # union
indA - indB  # difference
indA ^ indB  # symmetric difference

这些操作也可以通过对象的方法来访问,例如indA.intersection(indB)。至于更多关于Python中实现的各种集合操作的信息,请看第X.X节。那里列出的几乎每一种语法,除了修改集合的之外,都能在Index对象上进行操作。

前瞻

上面我们介绍了Series、DataFrame以及Index的基本介绍,它们构成了使用Pandas面向数据进行计算的基础。我们看到了它们与其他Python数据结构之间的异同点,以及它们可以怎样从这些熟悉的对象中创建出来。通读本章的内容之后,我们将对这些数据结构的创建(包括非常有用的从各种文件类型创建它们的接口)以及在这些结构上操作数据的细节做进一步的了解。正如了解如何有效地使用NumPy数组是在Python中高效进行数值计算的基础一样,理解如何有效地使用Pandas数据结构是Python数据科学中所要求的数据整理步骤的基础。

杰克•范德普拉斯(Jake VanderPlas)

杰克•范德普拉斯是Python科学计算组件的长期用户和开发者。他现在是华盛顿大学跨学科研究主管,他主要进行他自己的天文学研究,并在各个领域为的科学家提供建议和咨询。

Pandas table