【数据分析】第四章 pandas简介(2)
4.5 索引对象的其他功能
与 Python 常用数据结构相比,pandas 不仅利用了 NumPy 数组的高性能优势,还巧妙地整合了索引机制。
最终事实证明,这样做颇有几分成效。事实上,虽然已有的动态数据结构极为灵活,但在结构中增加诸如标签这样的内部索引机制,为接下来及下一章要讲解的一系列必要操作,提供了更为简单、直接的执行方法。
这一节,我们来详细分析几种使用索引机制实现的基础操作:
-
更换索引
-
删除
-
数据对齐
4.5.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.0
four 4.0
five NaN
one 2.0
dtype: float64
从返回结果可以看到,标签顺序全部调整过。删除了标签 two
及其元素,增加了新标签 five
,而 five
由于没有对应的值,被填充为 NaN
。请注意,当出现 NaN
值时,dtype
会自动提升为 float64
。
然而,重新编制索引时,定义所有的标签序列可能会很麻烦,对大型 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
对象,其索引列并不完整,而是缺失了几个值(例如1、2和4)。常见的需求是通过插值得到一个完整的序列。方法是使用 reindex()
函数,并将 method
选项的值设置为 'ffill'
(forward fill,向前填充)。此外,还需要指定索引值的范围。要指定一个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'
方法(backward fill,向后填充)。
>>> 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
对象,因为它在前面的操作中可能被修改过:
>>> 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)
>>> framecolor 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
现在,我们尝试对 frame
进行 reindex
操作。这里,我们尝试重新索引行(使用 range(5)
)并重新指定列(columns=['color', 'price', 'new', 'object']
)。
>>> frame.reindex(range(5), method='ffill', columns=['color', 'price', 'new', 'object'])color price new object
0 blue 1.2 NaN ball
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.5.2 删除
另一种与 Index
对象相关的操作是删除。因为索引和列名称都有了标签作为标识,所以删除操作变得非常简单。
pandas 专门提供了一个用于删除操作的函数:drop()
,它返回不包含已删除索引及其元素的新对象。
举例来说,我们想从 Series
对象中删除一项。为此,我们先来定义一个含有四个元素的 Series
对象,其中各元素标签均不相同。
>>> ser = pd.Series(np.arange(4.), index=['red', 'blue', 'yellow', 'white'])
>>> ser
red 0.0
blue 1.0
yellow 2.0
white 3.0
dtype: float64
假如我们想删除标签为 yellow
的这一项。用标签作为 drop()
函数的参数,就可以删除这一项。
>>> ser.drop('yellow')
red 0.0
blue 1.0
white 3.0
dtype: float64
传入一个由多个标签组成的列表,可以删除多项。
>>> ser.drop(['blue', 'white'])
red 0.0
yellow 2.0
dtype: float64
要删除 DataFrame
中的元素,需要指定元素所在的轴标签。我们通过具体的例子来看一下,首先声明一个 DataFrame
对象。
>>> frame = pd.DataFrame(np.arange(16).reshape((4,4)),
... index=['red', 'blue', 'yellow', 'white'],
... columns=['ball', 'pen', 'pencil', 'paper'])
>>> frameball 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
(或者 'columns'
)。
>>> frame.drop(['pen', 'pencil'], axis=1)ball paper
red 0 3
blue 4 7
yellow 8 11
white 12 15
4.5.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
。
>>