Pandas库
Pandas库是一个专门用于数据分析的开源Python库,有Series(序列)和DataFrame(数据框)这两种数据结构。
6 索引对象的其他功能
与Python常用数据结构相比,pandas不仅利用了NumPy数组的高性能优势,还整合了索引机制。
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对象,其索引列并不完整而是缺失了几个值(1、2和4)。常见的需求为插值,以得到一个完整的序列。方法是用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
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
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
7 数据结构之间的运算
接下来是两种及以上数据结构之间的运算。
上一节两个对象之间的算术运算。这一节,则对两种数据结构之间的运算进行深入探讨。
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。
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
8 函数应用和映射
pandas库函数。
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
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
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