之前自己对于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构造函数所能接受的各种数据。
类型 | 说明 |
---|---|
二维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列出了这些函数。
方法 | 说明 |
---|---|
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函数的各参数及说明。
参数 | 说明 |
---|---|
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 | 通过行和列标签选取单一值 |
整数索引
处理整数索引的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 | 用于指数(**)的方法 |
与此类似,在对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,而不是组中相同的元素数 |
带有重复标签的轴索引
直到目前为止,我所介绍的所有范例都有着唯一的轴标签(索引值)。虽然许多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列出了这些约简方法的常用选项。
选项 | 说明 |
---|---|
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列出了所有与描述统计相关的方法。
方法 | 说明 |
---|---|
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,其索引为唯一值,其值为频率,按计数值降序排列 |
有时,你可能希望得到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进行数据清洗、规整、分析和可视化工具。