数据基础---《利用Python进行数据分析·第2版》第5章 pandas入门

之前自己对于numpy和pandas是要用的时候东学一点西一点,直到看到《利用Python进行数据分析·第2版》,觉得只看这一篇就够了。非常感谢原博主的翻译和分享。

pandas是本书后续内容的首选库。它含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。pandas经常和其它工具一同使用,如数值计算工具NumPy和SciPy,分析库statsmodels和scikit-learn,和数据可视化库matplotlib。pandas是基于NumPy数组构建的,特别是基于数组的函数和不使用for循环的数据处理。

虽然pandas采用了大量的NumPy编码风格,但二者最大的不同是pandas是专门为处理表格和混杂数据设计的。而NumPy更适合处理统一的数值数组数据。

自从2010年pandas开源以来,pandas逐渐成长为一个非常大的库,应用于许多真实案例。开发者社区已经有了800个独立的贡献者,他们在解决日常数据问题的同时为这个项目提供贡献。

在本书后续部分中,我将使用下面这样的pandas引入约定:

import pandas as pd

因此,只要你在代码中看到pd.,就得想到这是pandas。因为Series和DataFrame用的次数非常多,所以将其引入本地命名空间中会更方便:

from pandas import Series, DataFrame

5.1 pandas的数据结构介绍

要使用pandas,你首先就得熟悉它的两个主要数据结构:Series和DataFrame。虽然它们并不能解决所有问题,但它们为大多数应用提供了一种可靠的、易于使用的基础。
(DataFrame可以看作是Series是堆叠)

Series

Series是一种类似于一维数组的对象,它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的Series:
numpy中的array也有索引,但是array索引只能是整数型,而且不会显示的打印出来;但是Series和DataFrame的索引可以是字符型,也能显示打印出来。

obj=pd.Series([4, 7, -5, 3])
obj
0    4
1    7
2   -5
3    3
dtype: int64

Series的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个0到N-1(N为数据的长度)的整数型索引。你可以通过Series 的values和index属性获取其数组表示形式和索引对象:

obj.index
RangeIndex(start=0, stop=4, step=1)

可以看到Index是一种数据对象,Series的索引、DataFrame的索引和列名都是Index数据对象。上面我们看到的是RangeIndex数据对象。

obj.values
array([ 4,  7, -5,  3], dtype=int64)

通常,我们希望所创建的Series带有一个可以对各个数据点进行标记的索引:因为用字符串命名更容易与业务的含义对应

obj2=pd.Series([4, 7, -5, 3],index=['d','b','a','c'])
obj2
d    4
b    7
a   -5
c    3
dtype: int64
obj2.index
Index(['d', 'b', 'a', 'c'], dtype='object')

与普通NumPy数组相比,你可以通过索引的方式选取Series中的单个或一组值:

obj2['a']
-5
obj2['d']=6
obj2
d    6
b    7
a   -5
c    3
dtype: int64

从上面可以看到对Series索引取数跟numpy中一样,也是获得一个视图,因而对对应位置的赋值会改变原Series对象,而不是返回一个新的对象。

obj2[['c', 'a', 'd']]
c    3
a   -5
d    6
dtype: int64

[‘c’, ‘a’, ‘d’]是索引列表,即使它包含的是字符串而不是整数。

obj2[['c', 'a', 'd']]=1
obj2
d    1
b    7
a    1
c    1
dtype: int64
obj2[['c', 'a', 'd']]['a']=0
obj2
d    1
b    7
a    1
c    1
dtype: int64
obj2[['c', 'a', 'd']]['a']
1

可以看到不管在numpy中还是在pandas中,不管是普通索引还是花式索引(我理解的花式索引就是通过数组来索引),不管是对一个轴进行索引还是对多个轴同时索引,其实获得的都是视图,对视图进行赋值都会改变原对象的值。但是一次索引之后紧接着二次索引,我们仍能获取对象的元素,此时对这些元素位置进行赋值不会改变原对象。该博主说它总是将数据复制到新数组中,既然是新的数组,当然就不会对原数据产生影响。

使用NumPy函数或类似NumPy的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值的链接:

obj2[obj2 > 1]
b    7
dtype: int64

上面虽然只有一个元素,但仍然是一个Series对象。obj2 > 1其实得到是一个布尔型索引,布尔型索引的使用和整数型索引和字符型索引是一样的。在numpy中和pd.DataFrame中也是同样适用的。

obj2 * 2
d     2
b    14
a     2
c     2
dtype: int64
obj2
d    1
b    7
a    1
c    1
dtype: int64
np.exp(obj2)
d       2.718282
b    1096.633158
a       2.718282
c       2.718282
dtype: float64
obj2
d    1
b    7
a    1
c    1
dtype: int64

可以看到在pandas中与在numpy中一样,对Series的函数操作是元素级的,并且与赋值不同,函数操作不会对原数据对象产生改变

还可以将Series看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中:索引相当于键

'b' in obj2
True
'e' in obj2
False

如果数据被存放在一个Python字典中,也可以直接通过这个字典来创建Series:

sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
obj3=pd.Series(sdata)
obj3
Ohio      35000
Oregon    16000
Texas     71000
Utah       5000
dtype: int64

如果只传入一个字典,则结果Series中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序,值的对应关系不会变,变的前后顺序,如果索引不在原字典中,对应的值就为空:

states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4=pd.Series(sdata,index=states)
obj4
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64

在这个例子中,sdata中跟states索引相匹配的那3个值会被找出来并放到相应的位置上,但由于"California"所对应的sdata值找不到,所以其结果就为NaN(即“非数字”(not a number),在pandas中,它用于表示缺失或NA值)。因为‘Utah’不在states中,它被从结果中除去。

我将使用缺失(missing)或NA表示缺失数据。pandas的isnull和notnull函数可用于检测缺失数据:

pd.isnull(obj4)
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool
pd.notnull(obj4)
California    False
Ohio           True
Oregon         True
Texas          True
dtype: bool

可以看到,得到的是bool值的series。Series也有类似的实例方法:

obj4.isnull()
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool

我将在第7章详细讲解如何处理缺失数据。

对于许多应用而言,Series最重要的一个功能是,它会根据运算的索引标签自动对齐数据:

obj3
Ohio      35000
Oregon    16000
Texas     71000
Utah       5000
dtype: int64
obj4
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64
obj3+obj4
California         NaN
Ohio           70000.0
Oregon         32000.0
Texas         142000.0
Utah               NaN
dtype: float64

首先会将两者的索引合并,然后将对应索引位置相加,如果某一方面有缺失值,则结果中对应位置的值也是缺失值。
数据对齐功能将在后面详细讲解。如果你使用过数据库,你可以认为是类似join的操作。

Series对象本身及其索引都有一个name属性,该属性跟pandas其他的关键功能关系非常密切:

obj4.name = 'population'
obj4.index.name = 'state'
obj4
state
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
Name: population, dtype: float64

Series的索引可以通过赋值的方式就地修改:

obj
0    4
1    7
2   -5
3    3
dtype: int64
obj.index=['Bob', 'Steve', 'Jeff', 'Ryan']
obj
Bob      4
Steve    7
Jeff    -5
Ryan     3
dtype: int64

DataFrame

DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,它可以被看做由Series组成的字典(共用同一个索引)。DataFrame中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。有关DataFrame内部的技术细节远远超出了本书所讨论的范围。

笔记:虽然DataFrame是以二维结构保存数据的,但你仍然可以轻松地将其表示为更高维度的数据(层次化索引的表格型结构,这是pandas中许多高级数据处理功能的关键要素,我们会在第8章讨论这个问题)。

建DataFrame的办法有很多,最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典:

data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year': [2000, 2001, 2002, 2001, 2002, 2003], 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame=pd.DataFrame(data)
frame
pop state year
0 1.5 Ohio 2000
1 1.7 Ohio 2001
2 3.6 Ohio 2002
3 2.4 Nevada 2001
4 2.9 Nevada 2002
5 3.2 Nevada 2003

如果你使用的是Jupyter notebook,pandas DataFrame对象会以对浏览器友好的HTML表格的方式呈现。

对于特别大的DataFrame,head方法会选取前五行:

frame.head()
pop state year
0 1.5 Ohio 2000
1 1.7 Ohio 2001
2 3.6 Ohio 2002
3 2.4 Nevada 2001
4 2.9 Nevada 2002

如果指定了列序列,则DataFrame的列就会按照指定顺序进行排列:

pd.DataFrame(data,columns=['year', 'state', 'pop'])
year state pop
0 2000 Ohio 1.5
1 2001 Ohio 1.7
2 2002 Ohio 3.6
3 2001 Nevada 2.4
4 2002 Nevada 2.9
5 2003 Nevada 3.2

如果传入的列在数据中找不到,就会在结果中产生缺失值:

frame2=pd.DataFrame(data,columns=['year', 'state', 'pop', 'debt'],index=['one', 'two', 'three', 'four','five', 'six'])
frame2
year state pop debt
one 2000 Ohio 1.5 NaN
two 2001 Ohio 1.7 NaN
three 2002 Ohio 3.6 NaN
four 2001 Nevada 2.4 NaN
five 2002 Nevada 2.9 NaN
six 2003 Nevada 3.2 NaN
frame2.columns
Index(['year', 'state', 'pop', 'debt'], dtype='object')
frame2.index
Index(['one', 'two', 'three', 'four', 'five', 'six'], dtype='object')

可以看到df.index和df.columns是一样的数据对象Index。很多性质和用法,在index上操作方法和在columns上是一样的。

通过类似字典标记的方式或属性的方式,可以将DataFrame的列获取为一个Series:

frame2['state']
one        Ohio
two        Ohio
three      Ohio
four     Nevada
five     Nevada
six      Nevada
Name: state, dtype: object
frame2.year
one      2000
two      2001
three    2002
four     2001
five     2002
six      2003
Name: year, dtype: int64

笔记:IPython提供了类似属性的访问(即frame2.year)和tab补全。
frame2[column]适用于任何列的名,但是frame2.column只有在列名是一个合理的Python变量名时才适用。

注意,返回的Series拥有原DataFrame相同的索引,且其name属性也已经被相应地设置好了。可以看到从DataFrame中取出来的Series比单纯生成的Series信息会更丰富一点。

行也可以通过位置或名称的方式进行获取,比如用loc属性(稍后将对此进行详细讲解):

frame2.loc['three']
year     2002
state    Ohio
pop       3.6
debt      NaN
Name: three, dtype: object

可以看到,loc或iloc是通过索引来操作数据的,也可以同时在[]中加入行索引和列索引,可以单独用行索引取一行确不能通过.loc单独取一列。

列可以通过赋值的方式进行修改。例如,我们可以给那个空的"debt"列赋上一个标量值或一组值:

frame2['debt']=16.5
frame2
year state pop debt
one 2000 Ohio 1.5 16.5
two 2001 Ohio 1.7 16.5
three 2002 Ohio 3.6 16.5
four 2001 Nevada 2.4 16.5
five 2002 Nevada 2.9 16.5
six 2003 Nevada 3.2 16.5
frame2['debt'] = np.arange(6.)
frame2
year state pop debt
one 2000 Ohio 1.5 0.0
two 2001 Ohio 1.7 1.0
three 2002 Ohio 3.6 2.0
four 2001 Nevada 2.4 3.0
five 2002 Nevada 2.9 4.0
six 2003 Nevada 3.2 5.0

将列表或数组赋值给某个列时,其长度必须跟DataFrame的长度相匹配。如果赋值的是一个Series,就会精确匹配DataFrame的索引,所有的空位都将被填上缺失值。在我看来Series和DataFrame没有太大差别

val=pd.Series([-1.2, -1.5, -1.7],index=['two', 'four', 'five'])
frame2['debt']=val
frame2
year state pop debt
one 2000 Ohio 1.5 NaN
two 2001 Ohio 1.7 -1.2
three 2002 Ohio 3.6 NaN
four 2001 Nevada 2.4 -1.5
five 2002 Nevada 2.9 -1.7
six 2003 Nevada 3.2 NaN

为不存在的列赋值会创建出一个新列。所以即使不用merge、john、concat、这些操作我们也能生成新的列。
关键字del用于删除列。
作为del的例子,我先添加一个新的布尔值的列,state是否为’Ohio’:

frame2['eastern']= frame2.state=='Ohio'
frame2
year state pop debt eastern
one 2000 Ohio 1.5 NaN True
two 2001 Ohio 1.7 -1.2 True
three 2002 Ohio 3.6 NaN True
four 2001 Nevada 2.4 -1.5 False
five 2002 Nevada 2.9 -1.7 False
six 2003 Nevada 3.2 NaN False

注意:不能用frame2.eastern创建新的列。

del方法可以用来删除这列:

del frame2['eastern']
frame2
year state pop debt
one 2000 Ohio 1.5 NaN
two 2001 Ohio 1.7 -1.2
three 2002 Ohio 3.6 NaN
four 2001 Nevada 2.4 -1.5
five 2002 Nevada 2.9 -1.7
six 2003 Nevada 3.2 NaN

注意:通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的Series所做的任何就地修改全都会反映到源DataFrame上。通过Series的copy方法即可指定复制列。

另一种常见的数据形式是嵌套字典:
如果嵌套字典传给DataFrame,pandas就会被解释为:外层字典的键作为列,内层键则作为行索引,而且会将内层索引补齐:

pop = {'Nevada': {2001: 2.4, 2002: 2.9}, 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
frame3=pd.DataFrame(pop)
frame3
Nevada Ohio
2000 NaN 1.5
2001 2.4 1.7
2002 2.9 3.6

你也可以使用类似NumPy数组的方法,对DataFrame进行转置(交换行和列):

frame3.T
2000 2001 2002
Nevada NaN 2.4 2.9
Ohio 1.5 1.7 3.6
frame3.T.index
Index(['Nevada', 'Ohio'], dtype='object')

内层字典的键会被合并、排序以形成最终的索引。如果明确指定了索引,则不会这样,指明了索引还是按之前Series和DataFrame一样,会用索引来找对应的值,以索引为准,而不是源数据,找不到的为空:

import pandas as pd
pop = {'Nevada': {2001: 2.4, 2002: 2.9},'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
pd.DataFrame(pop, index=pd.Index([2001, 2002, 2003]))
Nevada Ohio
2001 2.4 1.7
2002 2.9 3.6
2003 NaN NaN

在series中直接用index=[2000, 2002, 2001],原博主在DataFrame中也是直接index=[2000, 2002, 2001],但不知道我为什么出错,可能是python版本的问题。这里改为index=pd.Index([2001, 2002, 2003])

由Series组成的字典差不多也是一样的用法:

pdata = {'Ohio': frame3['Ohio'][:-1], 'Nevada': frame3['Nevada'][:2]}
pdata
{'Nevada': 2000    NaN
 2001    2.4
 Name: Nevada, dtype: float64, 'Ohio': 2000    1.5
 2001    1.7
 Name: Ohio, dtype: float64}
pd.DataFrame(pdata)
Nevada Ohio
2000 NaN 1.5
2001 2.4 1.7

表5-1列出了DataFrame构造函数所能接受的各种数据。

表5-1:可以输入给 DataFrame构造器的数据
类型 说明
二维ndarray 数据矩阵,还可以传入行标和列标
由数组、列表或元组组成的字典 每个序列会变成 Dataframe的一列。所有序列的长度必须相同
NumPy的结构化/记录数组 类似于“由数组组成的字典”
由 Series组成的字典 每个 Series会成为一列。如果没有显式指定索引,则各 Series的索引会被合并成结果的行索引
由字典组成的字典 各内层字典会成为一列。键会被合并成结果的行索引,跟“由 Series组成的字典”的情况一样
字典或 Series的列表 各项将会成为 Dataframe的一行。字典键或 Series索引的并集将会成为 Data frame的列标
由列表或元组组成的列表 类似于“二维 ndarray
另一个 Dataframe 该 Data frame的索引将会被沿用,除非显式指定了其他索引
NumPy的 Masked Array 类似于“二维 ndarray”的情况,只是掩码值在结果Dataframe会变成NA缺失值

如果设置了DataFrame的index和columns的name属性,则这些信息也会被显示出来:

frame3.index.name='year'
frame3.columns.name='state'
frame3
state Nevada Ohio
year
2000 NaN 1.5
2001 2.4 1.7
2002 2.9 3.6

跟Series一样,values属性也会以二维ndarray的形式返回DataFrame中的数据:

frame3.values
array([[nan, 1.5],
       [2.4, 1.7],
       [2.9, 3.6]])

如果DataFrame各列的数据类型不同,则值数组的dtype就会选用能兼容所有列的数据类型:

frame2.values
array([[2000, 'Ohio', 1.5, nan],
       [2001, 'Ohio', 1.7, nan],
       [2002, 'Ohio', 3.6, nan],
       [2001, 'Nevada', 2.4, nan],
       [2002, 'Nevada', 2.9, nan],
       [2003, 'Nevada', 3.2, nan]], dtype=object)

索引对象

pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index:

obj = pd.Series(range(3), index=['a', 'b', 'c'])
index = obj.index
index
Index(['a', 'b', 'c'], dtype='object')
index[1:]
Index(['b', 'c'], dtype='object')

Index对象是不可变的,因此用户不能对其进行修改:

index[1]='d'
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-18-8be6e68dba2d> in <module>()
----> 1 index[1]='d'


c:\users\qingt\miniconda2\envs\python35\lib\site-packages\pandas\core\indexes\base.py in __setitem__(self, key, value)
   2063 
   2064     def __setitem__(self, key, value):
-> 2065         raise TypeError("Index does not support mutable operations")
   2066 
   2067     def __getitem__(self, key):


TypeError: Index does not support mutable operations

不可变可以使Index对象在多个数据结构之间安全共享

import numpy as np
labels=pd.Index(np.arange(3))#主动生成index对象
labels
Int64Index([0, 1, 2], dtype='int64')
obj2 = pd.Series([1.5, -2.5, 0], index=labels)#可以直接赋一个列表,但这样更保险,不会出错
obj2
0    1.5
1   -2.5
2    0.0
dtype: float64
obj2.index is labels
True

注意:虽然用户不需要经常使用Index的功能,但是因为一些操作会生成包含被索引化的数据,理解它们的工作原理是很重要的。

除了类似于数组,Index的功能也类似一个固定大小的集合:

frame3
state Nevada Ohio
year
2000 NaN 1.5
2001 2.4 1.7
2002 2.9 3.6
frame3.columns
Index(['Nevada', 'Ohio'], dtype='object', name='state')
'Ohio' in frame3.columns
True
2003 in frame3.index
False

与python的集合不同,pandas的Index可以包含重复的标签:

dup_labels=pd.Index(pd.Index(['foo', 'foo', 'bar', 'bar']))
dup_labels
Index(['foo', 'foo', 'bar', 'bar'], dtype='object')

选择重复的标签,会显示所有的结果。

每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。表5-2列出了这些函数。

表5-2:ndex的方法和属性
方法 说明
append 连接另一个index对象,产生一个新的index
difference 计算差集,并得到一个index
intersection 计算交集
union 计算并集
isin 计算一个指示各值是否都包含在参数集合中的布尔型数组
delete 删除索引处的元素,并得到新的 Index
drop 删除传入的值,并得到新的ndex
Insert 将元素插入到索引处,并得到新的index
is_monotonic 当各元素均大于等于前一个元素时,返回True
is_unique 当index没有重复值时,返回True
unique 计算index中唯一值的数组

5.2 基本功能

本节中,我将介绍操作Series和DataFrame中的数据的基本手段。后续章节将更加深入地挖掘pandas在数据分析和处理方面的功能。本书不是pandas库的详尽文档,主要关注的是最重要的功能,那些不大常用的内容(也就是那些更深奥的内容)就交给你自己去摸索吧。

重新索引

pandas对象的一个重要方法是reindex,其作用是创建一个新对象,它的数据符合新的索引。看下面的例子:

obj=pd.Series([4.5, 7.2, -5.3, 3.6],index=['d', 'b', 'a', 'c'])
obj
d    4.5
b    7.2
a   -5.3
c    3.6
dtype: float64

用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存在,就引入缺失值;对原数据不产生影响,因为生成的是一个新的对象:

obj2=obj.reindex(['a', 'b', 'c', 'd', 'e'])
obj2
a   -5.3
b    7.2
c    3.6
d    4.5
e    NaN
dtype: float64

对于时间序列这样的有序数据,重新索引时可能需要做一些插值处理。method选项即可达到此目的,例如,使用ffill可以实现前向值填充(用前面一个值来填充):

obj3=pd.Series(['blue', 'purple', 'yellow'],index=[0, 2, 4])
obj3
0      blue
2    purple
4    yellow
dtype: object
obj3.reindex(range(6),method='ffill')
0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

借助DataFrame,reindex可以修改(行)索引和列。只传递一个序列时,会重新索引结果的行:

frame=pd.DataFrame(np.arange(9).reshape(3,3),index=['a', 'c', 'd'],columns=['Ohio', 'Texas', 'California'])
frame
Ohio Texas California
a 0 1 2
c 3 4 5
d 6 7 8
frame2=frame.reindex(['a', 'b', 'c', 'd'])
frame2
Ohio Texas California
a 0.0 1.0 2.0
b NaN NaN NaN
c 3.0 4.0 5.0
d 6.0 7.0 8.0

列可以用columns关键字重新索引:

states = ['Texas', 'Utah', 'California']
frame.reindex(columns=states)
Texas Utah California
a 1 NaN 2
c 4 NaN 5
d 7 NaN 8

整个Series和DataFrame都是一个套路,不管是在df里面设置index,还是用reindex来重设索引,对于原索引不在新索引会丢掉,新索引原索引没有的会填充空值。

表5-3列出了reindex函数的各参数及说明。

表5-3: reindex函数的参数
参数 说明
index 用作索引的新序列。既可以是index实例,也可以是其他序列型的 Python数据结构。 Index会被完全使用,就像没有任何复制一样
method 插值(填充)方式,具体参数请参见表5-4
fill_value 在重新索引的过程中,需要引入缺失值时使用的替代值
limit 前向或后向填充时的最大填充量
tolerance 向前后向后填充时,填充不准确匹配项的最大间距(绝对值距离)
level 在 MultiIndex的指定级别上匹配简单索引,否则选取其子集
copy 默认为True,无论如何都复制;如果为 False,则新旧相等就不复制

丢弃指定轴上的项

丢弃某条轴上的一个或多个项很简单,只要有一个索引数组或列表即可。由于需要执行一些数据整理和集合逻辑,所以drop方法返回的是一个在指定轴上删除了指定值的新对象:

obj=pd.Series(np.arange(5),index=['a', 'b', 'c', 'd', 'e'])
obj
a    0
b    1
c    2
d    3
e    4
dtype: int32
new_obj = obj.drop('c')
new_obj 
a    0
b    1
d    3
e    4
dtype: int32

对于DataFrame,可以删除任意轴上的索引值。为了演示,先新建一个DataFrame例子:

data=pd.DataFrame(np.arange(16).reshape(4,4),index=['Ohio', 'Colorado', 'Utah', 'New York'],columns=['one', 'two', 'three', 'four'])
data
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
Utah 8 9 10 11
New York 12 13 14 15

用标签序列调用drop会从行标签(axis 0)删除值:

data.drop(['Colorado', 'Ohio'])
one two three four
Utah 8 9 10 11
New York 12 13 14 15

通过传递axis=1或axis='columns’可以删除列的值,而且一定要传递axis=1,否则会出错:

data.drop('two',axis=1)
one three four
Ohio 0 2 3
Colorado 4 6 7
Utah 8 10 11
New York 12 14 15
data.drop(['two', 'four'],axis=1)
one three
Ohio 0 2
Colorado 4 6
Utah 8 10
New York 12 14

许多函数,如drop,会修改Series或DataFrame的大小或形状,通过设置inplace=True可以就地修改对象,不会返回新的对象:小心使用inplace,它会销毁所有被删除的数据。

obj.drop('c',inplace=True)
obj
a    0
b    1
d    3
e    4
dtype: int32

索引、选取和过滤

Series索引(obj[…])的工作方式类似于NumPy数组的索引,只不过Series的索引值不只是整数。下面是几个例子:

obj=pd.Series(np.arange(4.0),index=['a', 'b', 'c', 'd'])
obj
a    0.0
b    1.0
c    2.0
d    3.0
dtype: float64
obj['b']
1.0
obj[1]
1.0
obj[2:4]
c    2.0
d    3.0
dtype: float64
obj[['b','a','d']]
b    1.0
a    0.0
d    3.0
dtype: float64
obj[[1,3]]
b    1.0
d    3.0
dtype: float64
obj[obj<2]
a    0.0
b    1.0
dtype: float64

利用标签的切片运算与普通的Python切片运算不同,其末端是包含的:

obj['b':'c']
b    1.0
c    2.0
dtype: float64

用切片可以对Series的相应部分进行设置,跟numpy和DataFrame中的用法一样:

obj['b':'c']=5
obj
a    0.0
b    5.0
c    5.0
d    3.0
dtype: float64

用一个值或序列对DataFrame进行索引其实就是获取一个或多个列:

data = pd.DataFrame(np.arange(16).reshape((4, 4)),index=['Ohio', 'Colorado', 'Utah', 'New York'],columns=['one', 'two', 'three', 'four'])
data
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
Utah 8 9 10 11
New York 12 13 14 15
data['two']
Ohio         1
Colorado     5
Utah         9
New York    13
Name: two, dtype: int32
data[['three', 'one']]
three one
Ohio 2 0
Colorado 6 4
Utah 10 8
New York 14 12

DataFrame可以通过索引获取列,但却不能像data[‘Ohio’]或data[1]获取行。但可以有几个特殊的情况。首先通过切片或布尔型数组选取数据:

data[:2]
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
data[data['three'] > 5]
one two three four
Colorado 4 5 6 7
Utah 8 9 10 11
New York 12 13 14 15

选取行的语法data[:2]十分方便。向[ ]传递单一的元素(单一的元素似乎不行)或列表,就可选择行。另一种用法是通过布尔型DataFrame(比如下面这个由标量比较运算得出的)进行索引:

data < 5
one two three four
Ohio True True True True
Colorado True False False False
Utah False False False False
New York False False False False
data[data < 5] = 0
data
one two three four
Ohio 0 0 0 0
Colorado 0 5 6 7
Utah 8 9 10 11
New York 12 13 14 15

这使得DataFrame的语法与NumPy二维数组的语法很像。

用loc和iloc进行选取

对于DataFrame的行的标签索引,我引入了特殊的标签运算符loc和iloc。它们可以让你用类似NumPy的标记,使用轴标签(loc)或整数索引(iloc),从DataFrame选择行和列的子集。

作为一个初步示例,让我们通过标签选择一行和多列:

data.loc['Colorado',['two', 'three']]
two      5
three    6
Name: Colorado, dtype: int32
data.loc['Colorado']
one      0
two      5
three    6
four     7
Name: Colorado, dtype: int32

然后用iloc和整数进行选取:

data.iloc[2, [3, 0, 1]]
four    11
one      8
two      9
Name: Utah, dtype: int32
data.iloc[2]
one       8
two       9
three    10
four     11
Name: Utah, dtype: int32
data.iloc[[1, 2], [3, 0, 1]]
four one two
Colorado 7 0 5
Utah 11 8 9

可以看到,通过loc和iloc就可以单独获取行,所以可以统计使用这两个接口,不用把功能用乱了。而且切片也是视图,也可以对原数据对象产生改变

这两个索引函数也适用于一个标签或多个标签的切片:

data.loc[:'Utah', 'two']
Ohio        0
Colorado    5
Utah        9
Name: two, dtype: int32
data.iloc[:, :3][data.three > 5]
one two three
Colorado 0 5 6
Utah 8 9 10
New York 12 13 14

用:'Utah’是包含尾的,而用 :3则不包含尾

所以,在pandas中,有多个方法可以选取和重新组合数据。对于DataFrame,表5-4进行了总结。后面会看到,还有更多的方法进行层级化索引。

笔记:在一开始设计pandas时,我觉得用frame[:, col]选取列过于繁琐(也容易出错),因为列的选择是非常常见的操作。我做了些取舍,将花式索引的功能(标签和整数)放到了ix运算符中。在实践中,这会导致许多边缘情况,数据的轴标签是整数,所以pandas团队决定创造loc和iloc运算符分别处理严格基于标签和整数的索引。
ix运算符仍然可用,但并不推荐。

类型 说明
df[val] 从 DataFrame选取单列或一组列;在特殊情况下比较便利:布尔型数组(过滤行)、切片(行切片)、或布尔型 Dataframe(根据条件设置值)
df. loc[val] 通过标签,选取 Dataframe的单个行或一组行
df. loc[:,val] 通过标签,选取单列或列子集
df.loc[val1,val2] 通过标签,同时选取行和列
df.iloc[where] 通过整数位置,从 DataFrame选取单个行或行子集
df.iloc[:,where] 通过整数位置,从 Dataframe选取单个列或列子集
df.iloc[where_i,where_j] 通过整数位置,同时选取行和列
df.at[label_i,where_j] 通过行和列标签,选取单一的标量
df. iat [i,j] 通过行和列的位置(整数),选取单一的标量
reindex 通过标签选取行或列
ge_value,set_value 通过行和列标签选取单一值
表5-4 DataFrame的索引选项

整数索引

处理整数索引的pandas对象常常难住新手,因为它与Python内置的列表和元组的索引语法不同。例如,你可能不认为下面的代码会出错:

ser = pd.Series(np.arange(3.))
ser
0    0.0
1    1.0
2    2.0
dtype: float64

ser[-1]#KeyError: -1

这里,pandas可以勉强进行整数索引,但是会导致小bug。我们有包含0,1,2的索引,但是引入用户想要的东西(基于标签或位置的索引)很难:
另外,对于非整数索引,不会产生歧义:

ser2 = pd.Series(np.arange(3.), index=['a', 'b', 'c'])
ser2[-1]
2.0

前面为了进行统一,如果轴索引含有整数,数据选取总会使用标签。为了更准确,请使用loc(标签)或iloc(整数):

ser[:1]
0    0.0
dtype: float64
ser.loc[:1]
0    0.0
1    1.0
dtype: float64
ser.iloc[:1]
0    0.0
dtype: float64

可以看到规则还是一样的,字符型索引包含尾,.loc[:1]可以看成是字符型;而整数型索引则不包含尾iloc[:1]

算术运算和数据对齐

pandas最重要的一个功能是,它可以对不同索引的对象进行算术运算。在将对象相加时,如果存在不同的索引对,则结果的索引就是该索引对的并集。对于有数据库经验的用户,这就像在索引标签上进行自动外连接。看一个简单的例子:

s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1],index=['a', 'c', 'e', 'f', 'g'])
s1
a    7.3
c   -2.5
d    3.4
e    1.5
dtype: float64
s2
a   -2.1
c    3.6
e   -1.5
f    4.0
g    3.1
dtype: float64

将它们相加就会产生:

s1 + s2
a    5.2
c    1.1
d    NaN
e    0.0
f    NaN
g    NaN
dtype: float64

自动的数据对齐操作在不重叠的索引处引入了NA值。缺失值会在算术运算过程中传播。

对于DataFrame,对齐操作会同时发生在行和列上:

df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'),index=['Ohio', 'Texas', 'Colorado'])
df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),index=['Utah', 'Ohio', 'Texas', 'Oregon'])
df1
b c d
Ohio 0.0 1.0 2.0
Texas 3.0 4.0 5.0
Colorado 6.0 7.0 8.0
df2
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0

把它们相加后将会返回一个新的DataFrame,其索引和列为原来那两个DataFrame的并集:

df1 + df2
b c d e
Colorado NaN NaN NaN NaN
Ohio 3.0 NaN 6.0 NaN
Oregon NaN NaN NaN NaN
Texas 9.0 NaN 12.0 NaN
Utah NaN NaN NaN NaN

因为’c’和’e’列均不在两个DataFrame对象中,在结果中以缺省值呈现。行也是同样。

如果DataFrame对象相加,没有共用的列或行标签,结果都会是空:

df1 = pd.DataFrame({'A': [1, 2]})
df2 = pd.DataFrame({'B': [3, 4]})
df1
A
0 1
1 2
df2
B
0 3
1 4
df1 - df2
A B
0 NaN NaN
1 NaN NaN

在算术方法中填充值

在对不同索引的对象进行算术运算时,你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值(比如0):

df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)),columns=list('abcd'))
df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)),columns=list('abcde'))
df2.loc[1, 'b'] = np.nan
df1
a b c d
0 0.0 1.0 2.0 3.0
1 4.0 5.0 6.0 7.0
2 8.0 9.0 10.0 11.0
df2
a b c d e
0 0.0 1.0 2.0 3.0 4.0
1 5.0 NaN 7.0 8.0 9.0
2 10.0 11.0 12.0 13.0 14.0
3 15.0 16.0 17.0 18.0 19.0

将它们相加时,没有重叠的位置就会产生NA值:

df1 + df2
a b c d e
0 0.0 2.0 4.0 6.0 NaN
1 9.0 NaN 13.0 15.0 NaN
2 18.0 20.0 22.0 24.0 NaN
3 NaN NaN NaN NaN NaN

使用df1的add方法,传入df2以及一个fill_value参数:

df1.add(df2,fill_value=0)
a b c d e
0 0.0 2.0 4.0 6.0 4.0
1 9.0 5.0 13.0 15.0 9.0
2 18.0 20.0 22.0 24.0 14.0
3 15.0 16.0 17.0 18.0 19.0

所谓填充,就是哪一方缺失,哪一方就用这个值填充,然后再运算

表5-5列出了Series和DataFrame的算术方法。它们每个都有一个副本,以字母r开头,它会翻转参数。因此这两个语句是等价的:

1 / df1
a b c d
0 inf 1.000000 0.500000 0.333333
1 0.250000 0.200000 0.166667 0.142857
2 0.125000 0.111111 0.100000 0.090909
df1.rdiv(1)
a b c d
0 inf 1.000000 0.500000 0.333333
1 0.250000 0.200000 0.166667 0.142857
2 0.125000 0.111111 0.100000 0.090909
说明 方法
add. radd 用于加法(+)的方法
sub, rsub 用于减法(-)的方法
div, rdiv 用于除法(/的方法div, rdiv
floordiv,rfloordiv 用于底除(//)的方法
mul,rmul 用于乘法(*)的方法
Pow, rpow 用于指数(**)的方法
表5-5 灵活的算术方法

与此类似,在对Series或DataFrame重新索引时,也可以指定一个填充值:

df1.reindex(columns=df2.columns,fill_value=0)
a b c d e
0 0.0 1.0 2.0 3.0 0
1 4.0 5.0 6.0 7.0 0
2 8.0 9.0 10.0 11.0 0
df1
a b c d
0 0.0 1.0 2.0 3.0
1 4.0 5.0 6.0 7.0
2 8.0 9.0 10.0 11.0

DataFrame和Series之间的运算

跟不同维度的NumPy数组一样,DataFrame和Series之间算术运算也是有明确规定的。先来看一个具有启发性的例子,计算一个二维数组与其某行之间的差:

arr = np.arange(12.).reshape((3, 4))
arr
array([[ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.]])
 arr[0]
array([0., 1., 2., 3.])
arr - arr[0]
array([[0., 0., 0., 0.],
       [4., 4., 4., 4.],
       [8., 8., 8., 8.]])

当我们从arr减去arr[0],每一行都会执行这个操作。这就叫做广播(broadcasting),附录A将对此进行详细讲解。DataFrame和Series之间的运算差不多也是如此:

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),columns=list('bde'),index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.iloc[0]
frame
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0
series
b    0.0
d    1.0
e    2.0
Name: Utah, dtype: float64

默认情况下,DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列,然后沿着行一直向下广播:

frame - series
b d e
Utah 0.0 0.0 0.0
Ohio 3.0 3.0 3.0
Texas 6.0 6.0 6.0
Oregon 9.0 9.0 9.0
series2 = pd.Series(range(3), index=['b', 'e', 'f'])
series2
b    0
e    1
f    2
dtype: int64
frame + series2
b d e f
Utah 0.0 NaN 3.0 NaN
Ohio 3.0 NaN 6.0 NaN
Texas 6.0 NaN 9.0 NaN
Oregon 9.0 NaN 12.0 NaN

如果你希望匹配行且在列上广播,则必须使用算术运算方法。例如:

series3 = frame['d']
series3
Utah       1.0
Ohio       4.0
Texas      7.0
Oregon    10.0
Name: d, dtype: float64
frame
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0
frame.sub(series3, axis='index')
b d e
Utah -1.0 0.0 1.0
Ohio -1.0 0.0 1.0
Texas -1.0 0.0 1.0
Oregon -1.0 0.0 1.0

传入的轴号就是希望匹配的轴。在本例中,我们的目的是匹配DataFrame的行索引(axis=‘index’ or axis=0)并进行广播。

函数应用和映射

NumPy的ufuncs(元素级数组方法)也可用于操作pandas对象:

frame = pd.DataFrame(np.random.randn(4, 3), columns=list(‘bde’),index=[‘Utah’, ‘Ohio’, ‘Texas’, ‘Oregon’])

frame
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0
np.abs(frame)
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0

另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能:

f=lambda x:x.max()-x.min()
frame.apply(f)
b    9.0
d    9.0
e    9.0
dtype: float64

这里的函数f,计算了一个Series的最大值和最小值的差,在frame的每列都执行了一次。结果是一个Series,默认使用frame的列作为索引。

如果传递axis='columns’到apply,这个函数会在每行执行:

frame.apply(f, axis='columns')
Utah      2.0
Ohio      2.0
Texas     2.0
Oregon    2.0
dtype: float64

许多最为常见的数组统计功能都被实现成DataFrame的方法(如sum和mean),因此无需使用apply方法。

传递到apply的函数不是必须返回一个标量,还可以返回由多个值组成的Series:

def f(x):
    return pd.Series([x.max(),x.min()],index=['min', 'max'])
frame.apply(f)
b d e
min 9.0 10.0 11.0
max 0.0 1.0 2.0

像describe()就有点像这种情况的简化版

元素级的Python函数也是可以用的。假如你想得到frame中各个浮点值的格式化字符串,使用applymap即可:

format=lambda x:'%.2f'%x
frame.applymap(format)
b d e
Utah 0.00 1.00 2.00
Ohio 3.00 4.00 5.00
Texas 6.00 7.00 8.00
Oregon 9.00 10.00 11.00

之所以叫做applymap,是因为Series有一个用于应用元素级函数的map方法:

frame['e'].map(format)
Utah       2.00
Ohio       5.00
Texas      8.00
Oregon    11.00
Name: e, dtype: object

排序和排名

根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索引进行排序(按字典顺序),可使用sort_index方法,它将返回一个已排序的新对象:

obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])
obj
d    0
a    1
b    2
c    3
dtype: int64
obj.sort_index()
a    1
b    2
c    3
d    0
dtype: int64

对于DataFrame,则可以根据任意一个轴上的索引进行排序:

frame = pd.DataFrame(np.arange(8).reshape((2, 4)),index=['three', 'one'],columns=['d', 'a', 'b', 'c'])
frame
d a b c
three 0 1 2 3
one 4 5 6 7
frame.sort_index()
d a b c
one 4 5 6 7
three 0 1 2 3
frame.sort_index(axis=1)
a b c d
three 1 2 3 0
one 5 6 7 4

数据默认是按升序排序的,但也可以降序排序:

frame.sort_index(axis=1,ascending=False)
d c b a
three 0 3 2 1
one 4 7 6 5

若要按值对Series进行排序,可使用其sort_values方法:

obj = pd.Series([4, 7, -3, 2])
obj.sort_values()
2   -3
3    2
0    4
1    7
dtype: int64

在排序时,任何缺失值默认都会被放到Series的末尾:

obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])
obj.sort_values()
4   -3.0
5    2.0
0    4.0
2    7.0
1    NaN
3    NaN
dtype: float64

当排序一个DataFrame时,你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给sort_values的by选项即可达到该目的:

frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})
frame
a b
0 0 4
1 1 7
2 0 -3
3 1 2
frame.sort_values(by='b')
a b
2 0 -3
3 1 2
0 0 4
1 1 7

要根据多个列进行排序,传入名称的列表即可:

frame.sort_values(by=['a','b'])
a b
2 0 -3
0 0 4
3 1 2
1 1 7

排名会从1开始一直到数组中有效数据的数量。接下来介绍Series和DataFrame的rank方法。默认情况下,rank是通过“为各组分配一个平均排名”的方式破坏平级关系的:

obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
obj.rank()
0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64

rank()函数是排名函数,你只要先按1,2,……这样排一遍,然后并列的就用排名均值来表示

也可以根据值在原数据中出现的顺序给出排名:

obj.rank(method='first')
0    6.0
1    1.0
2    7.0
3    4.0
4    3.0
5    2.0
6    5.0
dtype: float64

这里,条目0和2没有使用平均排名6.5,它们被设成了6和7,因为数据中标签0位于标签2的前面。这个就相当于稳定排序

你也可以按降序进行排名:

obj.rank(ascending=False,method='max')
0    2.0
1    7.0
2    2.0
3    4.0
4    5.0
5    6.0
6    4.0
dtype: float64

表5-6列出了所有用于破坏平级关系的method选项。

DataFrame可以在行或列上计算排名:

frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], 'c': [-2, 5, 8, -2.5]})
frame
a b c
0 0 4.3 -2.0
1 1 7.0 5.0
2 0 -3.0 8.0
3 1 2.0 -2.5
frame.rank(axis='columns')
a b c
0 2.0 3.0 1.0
1 1.0 3.0 2.0
2 2.0 1.0 3.0
3 2.0 3.0 1.0
方法 说明
‘average’ 默认、在相等分组中,为各个值分平均排名
‘min’ 使用整个分组的最小排名
‘max’ 使用整个分组的最大排名
‘first’ 按值在原始数据中的出现顺序分配排名
‘dense’ 类似于’min’,但是排名总是在组间增加1,而不是组中相同的元素数
表5-6 排名时用于破坏平级关系的方法

带有重复标签的轴索引

直到目前为止,我所介绍的所有范例都有着唯一的轴标签(索引值)。虽然许多pandas函数(如reindex)都要求标签唯一,但这并不是强制性的。我们来看看下面这个简单的带有重复索引值的Series:

obj = pd.Series(range(5), index=['a', 'a', 'b', 'b', 'c'])
obj
a    0
a    1
b    2
b    3
c    4
dtype: int64

索引的is_unique属性可以告诉你它的值是否是唯一的:

obj.index.is_unique
False

对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应多个值,则返回一个Series;而对应单个值的,则返回一个标量值:

obj['a']
a    0
a    1
dtype: int64
obj['c']
4

这样会使代码变复杂,因为索引的输出类型会根据标签是否有重复发生变化。

对DataFrame的行进行索引时也是如此:

df = pd.DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b'])
df
0 1 2
a -0.375736 -1.663318 -0.327571
a 0.521448 0.679046 -0.689582
b 1.814268 -0.912583 -0.100925
b -0.961466 -0.026515 -0.000365
df.loc['b']
0 1 2
b 1.814268 -0.912583 -0.100925
b -0.961466 -0.026515 -0.000365

5.3 汇总和计算描述统计

pandas对象拥有一组常用的数学和统计方法。它们大部分都属于约简和汇总统计,用于从Series中提取单个值(如sum或mean)或从DataFrame的行或列中提取一个Series。跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的(当有缺失值时,会先把缺失值排除在外,不会报错)。看一个简单的DataFrame:

df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5],[np.nan, np.nan], [0.75, -1.3]],index=['a', 'b', 'c', 'd'],columns=['one', 'two'])
df
one two
a 1.40 NaN
b 7.10 -4.5
c NaN NaN
d 0.75 -1.3

调用DataFrame的sum方法将会返回一个含有列的和的Series:DataFrame很多操作都是默认对列操作的

df.sum()
one    9.25
two   -5.80
dtype: float64

传入axis='columns’或axis=1将会按行进行求和运算:

df.sum(axis=1)
a    1.40
b    2.60
c    0.00
d   -0.55
dtype: float64

NA值会自动被排除,除非整个切片(这里指的是行或列)都是NA。通过skipna选项可以禁用该功能:

df.mean(axis='columns',skipna=False)
a      NaN
b    1.300
c      NaN
d   -0.275
dtype: float64

表5-7列出了这些约简方法的常用选项。

表5-7:约简方法的选项
选项 说明
axis 约简的轴。 Dataframef的行用0,列用1
skipna 排除缺失值,默认值为True
level 如果轴是层次化索引的(即 Multiindex),则根据 Levels分组约简

有些方法(如idxmin和idxmax)返回的是间接统计(比如达到最小值或最大值的索引):

df.idxmax()
one    b
two    d
dtype: object

另一些方法则是累计型的:

df.cumsum()
one two
a 1.40 NaN
b 8.50 -4.5
c NaN NaN
d 9.25 -5.8

还有一种方法,它既不是约简型也不是累计型。describe就是一个例子,它用于一次性产生多个汇总统计:

df.describe()
one two
count 3.000000 2.000000
mean 3.083333 -2.900000
std 3.493685 2.262742
min 0.750000 -4.500000
25% 1.075000 -3.700000
50% 1.400000 -2.900000
75% 4.250000 -2.100000
max 7.100000 -1.300000

对于非数值型数据,describe会产生另外一种汇总统计:

obj = pd.Series(['a', 'a', 'b', 'c'] * 4)
obj
0     a
1     a
2     b
3     c
4     a
5     a
6     b
7     c
8     a
9     a
10    b
11    c
12    a
13    a
14    b
15    c
dtype: object
obj.describe()
count     16
unique     3
top        a
freq       8
dtype: object

表5-8列出了所有与描述统计相关的方法。

表5-8:描述和汇总统计
方法 说明
count 非NA值的数量
describe 针对 Series或各 Dataframe列计算汇总统计
min、max 计算最小值和最大值
argmin、 argmax 计算能够获取到最小值和最大值的素引位置(整数)
idxmin、 idxmax 计算能够获取到最小值和最大值的索引值
quantile 计算样本的分位数(0到1)
sum 值的总和
mean 值的平均数
median 值的算术中位数(50%分位数)
mad 根据平均值计算平均绝对离差
var 样本值的方差
std 样本值的标准差
skew 样本值的偏度(三阶矩)
kurt 样本值的峰度(四阶矩)
cumsum 样本值的计和
cummin cummax 样本值的累计最大值和计最小值
cumprod 样本值的素计积
diff 计算一阶差分(对时间序列很有用)
pct_change 计算百分数变化(当前数比上一个数增长的百分比)

相关系数与协方差

有些汇总统计(如相关系数和协方差)是通过参数对计算出来的。我们来看几个DataFrame,它们的数据来自Yahoo!Finance的股票价格和成交量,使用的是pandas-datareader包(可以用conda或pip安装):
conda install pandas-datareader
我使用pandas_datareader模块下载了一些股票数据:

import pandas_datareader.data as web
all_data = {ticker: web.get_data_yahoo(ticker) for ticker in ['AAPL', 'IBM', 'MSFT', 'GOOG']}
price = pd.DataFrame({ticker: data['Adj Close'] for ticker, data in all_data.items()})
volume = pd.DataFrame({ticker: data['Volume'] for ticker, data in all_data.items()})
price.head(10)
AAPL GOOG IBM MSFT
Date
2009-12-31 26.680096 307.986847 101.739494 24.450689
2010-01-04 27.095369 311.349976 102.944206 24.827723
2010-01-05 27.142210 309.978882 101.700638 24.835745
2010-01-06 26.710482 302.164703 101.039986 24.683319
2010-01-07 26.661104 295.130463 100.690277 24.426620
2010-01-08 26.838356 299.064880 101.700638 24.595085
2010-01-11 26.601601 298.612823 100.635834 24.282234
2010-01-12 26.299009 293.332153 101.436394 24.121790
2010-01-13 26.669970 291.648102 101.218758 24.346409
2010-01-14 26.515507 293.019196 102.835388 24.835745
volume.head(10)
AAPL GOOG IBM MSFT
Date
2009-12-31 88102700.0 2455400.0 4223400.0 31929700.0
2010-01-04 123432400.0 3937800.0 6155300.0 38409100.0
2010-01-05 150476200.0 6048500.0 6841400.0 49749600.0
2010-01-06 138040000.0 8009000.0 5605300.0 58182400.0
2010-01-07 119282800.0 12912000.0 5840600.0 50559700.0
2010-01-08 111902700.0 9509900.0 4197200.0 51197400.0
2010-01-11 115557400.0 14519600.0 5730400.0 68754700.0
2010-01-12 148614900.0 9769600.0 8081500.0 65912100.0
2010-01-13 151473000.0 13077600.0 6455400.0 51863500.0
2010-01-14 108223500.0 8535300.0 7111800.0 63228100.0

注意:此时Yahoo! Finance已经不存在了,因为2017年Yahoo!被Verizon收购了。参阅pandas-datareader文档,可以学习最新的功能。

现在计算价格的百分数变化,时间序列的操作会在第11章介绍:

returns = price.pct_change()
returns.tail()
AAPL GOOG IBM MSFT
Date
2018-09-24 0.014380 0.006243 -0.008722 0.003588
2018-09-25 0.006341 0.009613 -0.007465 -0.001919
2018-09-26 -0.007966 -0.003512 0.018132 -0.004107
2018-09-27 0.020552 0.011987 -0.000726 0.003773
2018-09-28 0.002423 -0.002645 -0.000396 -0.001573

Series的corr方法用于计算两个Series中重叠的、非NA的、按索引对齐的值的相关系数。与此类似,cov用于计算协方差:

returns['MSFT'].corr(returns['IBM'])
0.4725684602482829
returns['MSFT'].cov(returns['IBM'])
7.949488555937081e-05

因为MSTF是一个合理的Python属性,我们还可以用更简洁的语法选择列:

returns.MSFT.corr(returns.IBM)
0.4725684602482829

另一方面,DataFrame的corr和cov方法将以DataFrame的形式分别返回完整的相关系数或协方差矩阵:

returns.corr()
AAPL GOOG IBM MSFT
AAPL 1.000000 0.430543 0.360614 0.413546
GOOG 0.430543 1.000000 0.393899 0.508093
IBM 0.360614 0.393899 1.000000 0.472568
MSFT 0.413546 0.508093 0.472568 1.000000

默认对列操作,各列两两之间的相关系数

returns.cov()
AAPL GOOG IBM MSFT
AAPL 0.000250 0.000103 0.000068 0.000092
GOOG 0.000103 0.000230 0.000071 0.000109
IBM 0.000068 0.000071 0.000142 0.000079
MSFT 0.000092 0.000109 0.000079 0.000199

利用DataFrame的corrwith方法,你可以计算其列或行跟另一个Series或DataFrame之间的相关系数。传入一个Series将会返回一个相关系数值Series(针对各列进行计算):

returns.corrwith(returns.IBM)
AAPL    0.360614
GOOG    0.393899
IBM     1.000000
MSFT    0.472568
dtype: float64

传入一个DataFrame则会计算按列名配对的相关系数。这里,我计算百分比变化与成交量的相关系数:

returns.corrwith(volume)
AAPL   -0.066751
GOOG   -0.018462
IBM    -0.158417
MSFT   -0.086733
dtype: float64

注意是两两配对好的相关系数

传入axis='columns’即可按行进行计算。无论如何,在计算相关系数之前,所有的数据项都会按标签对齐。

唯一值、值计数以及成员资格

还有一类方法可以从一维Series的值中抽取信息。看下面的例子:

obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])

第一个函数是unique,它可以得到Series中的唯一值数组:

uniques = obj.unique()
uniques
array(['c', 'a', 'd', 'b'], dtype=object)

返回的唯一值是未排序的,如果需要的话,可以对结果再次进行排序(uniques.sort())。相似的,value_counts用于计算一个Series中各值出现的频率:

obj.value_counts()
c    3
a    3
b    2
d    1
dtype: int64

为了便于查看,结果Series是按值频率降序排列的。value_counts还是一个顶级pandas方法,可用于任何数组或序列:

pd.value_counts(obj.values,sort=False)
a    3
b    2
c    3
d    1
dtype: int64

isin用于判断矢量化集合的成员资格,可用于过滤Series中或DataFrame列中数据的子集:

obj
0    c
1    a
2    d
3    a
4    a
5    b
6    b
7    c
8    c
dtype: object
mask = obj.isin(['b','c'])
mask
0     True
1    False
2    False
3    False
4    False
5     True
6     True
7     True
8     True
dtype: bool

相当于将Series中的元素逐个查看是否在[‘b’,‘c’]中

obj[mask]
0    c
5    b
6    b
7    c
8    c
dtype: object

与isin类似的是Index.get_indexer方法,它可以给你一个索引数组,从可能包含重复值的数组到另一个不同值的数组:

to_match = pd.Series(['c', 'a', 'b', 'b', 'c', 'a'])
unique_vals = pd.Series(['c', 'b', 'a'])
pd.Index(unique_vals).get_indexer(to_match)
array([0, 2, 1, 1, 0, 2], dtype=int64)

似乎get_indexer和isin的用法是反的,isin是检查前面这个数据对象的元素是否在后面的这个数据对象中;get_indexer就检查后面这个数据对象的元素在前面这个索引对象的位置

表5-9给出了这几个方法的一些参考信息。

说明 方法
isin 计算一个表示“ Series各值是否包含于传入的值序列中”的布尔型数组
match 计算一个数组中的各值到另一个不同值数组的整数索引:对于数据对齐和连接类型的操作十分有用
unique 计算 Series中的唯一值数组,按发现的顺序返回
value_cout 返回一个Series,其索引为唯一值,其值为频率,按计数值降序排列
表5-9 唯一值、值计数、成员资格方法

有时,你可能希望得到DataFrame中多个相关列的一张柱状图。例如:

data = pd.DataFrame({'Qu1': [1, 3, 4, 3, 4],'Qu2': [2, 3, 1, 2, 3],'Qu3': [1, 5, 2, 4, 4]})
data
Qu1 Qu2 Qu3
0 1 2 1
1 3 3 5
2 4 1 2
3 3 2 4
4 4 3 4

将pandas.value_counts传给该DataFrame的apply函数,就会出现:

result = data.apply(pd.value_counts)
result
Qu1 Qu2 Qu3
1 1.0 1.0 1.0
2 NaN 2.0 1.0
3 2.0 2.0 NaN
4 2.0 NaN 2.0
5 NaN NaN 1.0

可以看到默认是按列操作,而且它不仅仅对各列统计,而且统计的元素包含于整个dataframe中的元素,这些元素在各列出现的次数,如果没有出现就是空值NaN

这里,结果中的行标签是所有列的唯一值。后面的频率值是每个列中这些值的相应计数。

5.4 总结

在下一章,我们将讨论用pandas读取(或加载)和写入数据集的工具。

之后,我们将更深入地研究使用pandas进行数据清洗、规整、分析和可视化工具。

猜你喜欢

转载自blog.csdn.net/qingqing7/article/details/82891805