从本质上来说,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数据科学中所要求的数据整理步骤的基础。