通过前面几个章节的铺垫,我们对于数据处理也有了一点了解。后续主要首选是Pandas,它包含了使数据清洗和分析工作更快更简单的数据结构和操作工具。pandas经常会和其他工具一起并行使用,如上章学习到的numpy和scipy,分析库statsmodels和scikit-learn,和数据可视化库matplotlib。pandas是基于numpy建立的,特别是对基于数组的函数和不使用for循环的数据处理。
虽然pandas使用了很多的numpy编码风格,但是二者最大的不同是pandas专门为处理表格和混杂数据设计的框架。而numpy更加适合处理统一的数值数组数据。
本章中,我将使用以下约定引入pandas:
In [1]: import pandas as pd
因为Series和DataFrame用的次数非常多,所以将其引入本地命名空间中会更方便:
In [2]: from pandas import Series, DataFrame
5.1pandas的数据结构
要使用pandas,你首先就得熟悉它的两个主要数据结构:Series和DataFrame。这两个基本数可以解决大多数的处理问题了,所以我们要了解清楚他们的细节。
Series
Series是一种类似一维数组的对象,它是由一组数据(各种numpy的数据类型)以及一组与之相关的数据标签(索引)组成。
In [11]: obj = pd.Series([4, 7, -5, 3])
In [12]: obj
Out[12]:
0 4
1 7
2 -5
3 3
dtype: int64
Series的表现形式是左边是索引,右边是数据值。这里我们并没有对数据进行特殊索引设置,所以是默认从0开始的索引标号。你也可以通过Series的values和index属性对数组内容进行访问:
In [13]: obj.values
Out[13]: array([ 4, 7, -5, 3])
In [14]: obj.index # like range(4)
Out[14]: RangeIndex(start=0, stop=4, step=1)
通常来说,我们希望Series可以对各个数据进行标记:
In [15]: obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
In [16]: obj2
Out[16]:
d 4
b 7
a -5
c 3
dtype: int64
In [17]: obj2.index
Out[17]: Index(['d', 'b', 'a', 'c'], dtype='object')
与普通NumPy数组相比,你可以通过索引的方式选取Series中的单个或一组值:
In [18]: obj2['a']
Out[18]: -5
In [19]: obj2['d'] = 6
In [20]: obj2[['c', 'a', 'd']]
Out[20]:
c 3
a -5
d 6
dtype: int64
当然你也可以通过上章节所学的numpy的相关知识对Series进行数据运算:
In [21]: obj2[obj2 > 0]
Out[21]:
d 6
b 7
c 3
dtype: int64
In [22]: obj2 * 2
Out[22]:
d 12
b 14
a -10
c 6
dtype: int64
In [23]: np.exp(obj2)
Out[23]:
d 403.428793
b 1096.633158
a 0.006738
c 20.085537
dtype: float64
如果数据被存放在一个Python字典中,也可以直接通过这个字典来创建Series:
In [26]: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
In [27]: obj3 = pd.Series(sdata)
In [28]: obj3
Out[28]:
Ohio 35000
Oregon 16000
Texas 71000
Utah 5000
dtype: int64
如果只传入一个字典,则结果Series中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序:
In [29]: states = ['California', 'Ohio', 'Oregon', 'Texas']
In [30]: obj4 = pd.Series(sdata, index=states)
In [31]: obj4
Out[31]:
California NaN
Ohio 35000.0
Oregon 16000.0
Texas 71000.0
dtype: float64
这个例子中,sdata和states索引相匹配的才会存入新生成的对象中,由于states中的California
找不到对应值,所以为NaN。而Utah
并不在states中,所以也不存在。
这种NaN在pandas中被称为损失值或NA值。pandas有函数isnull和notnull进行检测数组中是否含有损失值:
In [32]: pd.isnull(obj4)
Out[32]:
California True
Ohio False
Oregon False
Texas False
dtype: bool
In [33]: pd.notnull(obj4)
Out[33]:
California False
Ohio True
Oregon True
Texas True
dtype: bool
当然细心的同学可能发现了Series很重要的功能就是数据对齐,数据能够很整齐的排列,这对我们数据分析有很大帮助。Series对象本身和其索引都有一个属性name,该属性和pandas的其他功能密切相关:
In [38]: obj4.name = 'population'
In [39]: obj4.index.name = 'state'
In [40]: obj4
Out[40]:
state
California NaN
Ohio 35000.0
Oregon 16000.0
Texas 71000.0
Name: population, dtype: float64
同时Series的索引可以通过赋值就地修改:
In [41]: obj
Out[41]:
0 4
1 7
2 -5
3 3
dtype: int64
In [42]: obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan']
In [43]: obj
Out[43]:
Bob 4
Steve 7
Jeff -5
Ryan 3
dtype: int64
DataFrame
DataFrame是一个表格型数据结构,它含有一组有序列,每列可以识不同类型值。DataFrame既有行索引,也有列索引,它可以被看成是Series组成的字典。DataFrame中的数据是以一个或多个二维块存放的,而不是列表,字典或者其他的一维数组的集合。关于其内部实现,这超出了本章的范围,所以有兴趣的小伙伴可以自行查阅了解细节。
学一个新的数据结构首先就是要学会它如何构造。有很多方式都可以构造DataFrame,最常用的方式就是利用包含等长度列表或Numpy数组的字典来形成DataFrame:
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)
结果DataFrame会自动加上索引(跟Series一样):
In [24]:frame
Out[24]:
state year pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
5 Nevada 2003 3.2
如果你使用的是Jupyter notebook,pandas DataFrame对象会以对浏览器友好的HTML表格的方式呈现。
对于特别大的DataFrame,head方法会选取前五行:
In [26]:frame.head()
Out[27]:
state year pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
如果你想要自定义列序,你需要制定好序列顺序:
In [27]:pd.DataFrame(data, columns=['year', 'state', 'pop'])
Out[28]:
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
如果传入的列在数据中找不到,就会在结果中产生缺失值:
In [28]:pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'],
....: index=['one', 'two', 'three', 'four',
....: 'five', 'six'])
Out[29]:
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
通过类似字典标记的方式或属性的方式,可以将DataFrame的列获取为一个Series:
In [36]:frame2['year']
Out[33]:
one 2000
two 2001
three 2002
four 2001
five 2002
six 2003
Name: year, dtype: int64
In [36]:frame2.state
Out[34]:
one Ohio
two Ohio
three Ohio
four Nevada
five Nevada
six Nevada
Name: state, dtype: object
行也可以通过位置或名称方式进行获取,比如用到loc属性:
In [36]:frame2.loc['three']
Out[36]:
year 2002
state Ohio
pop 3.6
debt NaN
Name: three, dtype: object
列可以通过赋值的方式进行修改。例如,我们可以给那个空的"debt"列赋上一个标量值或一组值:
In [37]: frame2['debt'] = 16
In [38]:frame2
Out[38]:
year state pop debt
one 2000 Ohio 1.5 16
two 2001 Ohio 1.7 16
three 2002 Ohio 3.6 16
four 2001 Nevada 2.4 16
five 2002 Nevada 2.9 16
six 2003 Nevada 3.2 16
In [39]: import numpy as np
In [40]: frame2['debt'] = np.arange(6.)
In [41]: frame2
Out[41]:
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的索引,所有的空位都将被填上缺失值:
In [42]: val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])
In [43]: frame2['debt'] = val
In [44]: frame2
Out[44]:
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
为不存在的列赋值会创建出一个新列。关键字del用于删除列。作为del的例子,我先添加一个新的布尔值的列,state是否为’Ohio’:
In [45]: frame2['ear'] = frame2.state == 'Ohio'
In [46]: frame2
Out[46]:
year state pop debt ear
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方法可以用来删除这列:
In [47]: del frame2['ear']
In [48]: frame2.columns
Out[48]: Index(['year', 'state', 'pop', 'debt'], dtype='object')
注意:通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的Series所做的任何就地修改全都会反映到源DataFrame上。通过Series的copy方法即可指定复制列。
另一种常见的数据形式是嵌套字典:
In [49]: pop = {'Nevada': {2001: 2.4, 2002: 2.9},
....: 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
如果嵌套字典传给DataFrame,pandas就会被解释为:外层字典的键作为列,内层键则作为行索引:
In [50]: frame3 = pd.DataFrame(pop)
In [51]: frame3
Out[52]:
Nevada Ohio
2000 NaN 1.5
2001 2.4 1.7
2002 2.9 3.6
你也可以使用类似NumPy数组的方法,对DataFrame进行转置(交换行和列):
In [53]: frame3.T
Out[53]:
2000 2001 2002
Nevada NaN 2.4 2.9
Ohio 1.5 1.7 3.6
索引对象
pandas的索引对象负责管理轴标签和其他的元数据(例如轴名称)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换为Index:
In [76]: obj = pd.Series(range(3), index=['a', 'b', 'c'])
In [77]: index = obj.index
In [78]: index
Out[78]: Index(['a', 'b', 'c'], dtype='object')
In [79]: index[1:]
Out[79]: Index(['b', 'c'], dtype='object')
Index对象是不可变的,因此用户不能对其进行修改。不可变可以使Index对象在多个数据结构之间安全共享:
In [80]: labels = pd.Index(np.arange(3))
In [81]: labels
Out[81]: Int64Index([0, 1, 2], dtype='int64')
In [82]: obj2 = pd.Series([1.5, -2.5, 0], index=labels)
In [83]: obj2
Out[83]:
0 1.5
1 -2.5
2 0.0
dtype: float64
In [84]: obj2.index is labels
Out[84]: True
下图表5-2列出了索引常见的方法和属性
5.2基本功能
基础概念介绍完毕了,接下来要对数据操作的基本手段进行一个解决,本文并不是罗列所有的pandas库,所以只展示一些常用函数,如果要深入学习,可以自己仔细阅读相关文档进一步学习。
重建索引
reindex是pandas对象的重要方法,该方法用于创建一个符合新索引的新对象:
In [91]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])
In [92]: obj
Out[92]:
d 4.5
b 7.2
a -5.3
c 3.6
dtype: float64
用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存在,就引入缺失值:
In [93]: obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
In [94]: obj2
Out[94]:
a -5.3
b 7.2
c 3.6
d 4.5
e NaN
dtype: float64
对于顺序结构的数据,比如递增函数,在重建索引的时候会需要进行插值和填充。method方法可以允许我们对其插入,例如ffill方法,会在值前插入:
In [95]: obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
In [96]: obj3
Out[96]:
0 blue
2 purple
4 yellow
dtype: object
In [97]: obj3.reindex(range(6), method='ffill')
Out[97]:
0 blue
1 blue
2 purple
3 purple
4 yellow
5 yellow
dtype: object
在DataFrame中,reindex仍然可以更改行与列的索引值。只需要传递一个序列,结果就会重置:
In [98]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)),
....: index=['a', 'c', 'd'],
....: columns=['Ohio', 'Texas', 'California'])
In [99]: frame
Out[99]:
Ohio Texas California
a 0 1 2
c 3 4 5
d 6 7 8
In [100]: frame2 = frame.reindex(['a', 'b', 'c', 'd'])
In [101]: frame2
Out[101]:
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关键字重新索引:
In [102]: states = ['Texas', 'Utah', 'California']
In [103]: frame.reindex(columns=states)
Out[103]:
Texas Utah California
a 1 NaN 2
c 4 NaN 5
d 7 NaN 8
轴向上删除条目
如果此时你已经拥有了索引数组或不含条目的列表,在轴向上删除一个或更多的条目就很容易,但这样需要一些数据操作和集合逻辑,drop方法会返回一个含有指示值或轴向上删除值的新对象:
In [105]: obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
In [106]: obj
Out[106]:
a 0.0
b 1.0
c 2.0
d 3.0
e 4.0
dtype: float64
In [107]: new_obj = obj.drop('c')
In [108]: new_obj
Out[108]:
a 0.0
b 1.0
d 3.0
e 4.0
dtype: float64
In [109]: obj.drop(['d', 'c'])
Out[109]:
a 0.0
b 1.0
e 4.0
dtype: float64
在DataFrame中,有类似的属性方法,具体就不再演示了,大家可以自己尝试。不过要小心使用drop函数中的inplace属性,小心使用inplace,它会销毁所有被删除的数据。‘
索引、选取和过滤
Series的索引工作方式类似于Numpy,只不过Series有更丰富的索引类型:
In [117]: obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
In [118]: obj
Out[118]:
a 0.0
b 1.0
c 2.0
d 3.0
dtype: float64
In [119]: obj['b']
Out[119]: 1.0
In [120]: obj[1]
Out[120]: 1.0
In [121]: obj[2:4]
Out[121]:
c 2.0
d 3.0
dtype: float64
In [122]: obj[['b', 'a', 'd']]
Out[122]:
b 1.0
a 0.0
d 3.0
dtype: float64
In [123]: obj[[1, 3]]
Out[123]:
b 1.0
d 3.0
dtype: float64
In [124]: obj[obj < 2]
Out[124]:
a 0.0
b 1.0
dtype: float64
DataFrame的操作方法类似Series。同样可以通过上述方法进行选择。这使得DataFrame的语法与NumPy二维数组的语法很像。
用loc和iloc进行选取
对于DataFrame的行标签,pandas引入了loc和iloc,它们可以让使用者能够通过类似与numpy的标记方式,使用轴标签(loc)或整数标签(iloc),从DataFrame选择行和列的子集。以下作一个初步示例,我们来通过标签选择一行或多列:
In [137]: data.loc['Colorado', ['two', 'three']]
Out[137]:
two 5
three 6
Name: Colorado, dtype: int64
然后用iloc和整数进行选取,数字就代表行与列:
In [138]: data.iloc[2, [3, 0, 1]]
Out[138]:
four 11
one 8
two 9
Name: Utah, dtype: int64
In [139]: data.iloc[2]
Out[139]:
one 8
two 9
three 10
four 11
Name: Utah, dtype: int64
In [140]: data.iloc[[1, 2], [3, 0, 1]]
Out[140]:
four one two
Colorado 7 0 5
Utah 11 8 9
这两个索引函数也适用于一个标签或多个标签的切片。所以,在pandas中,有多个方法可以选取和重新组合数据。对于DataFrame,图5-1进行了总结。后面会看到,还有更多的方法进行层级化索引。
由于内容太多这里分一篇!!