pandas的数据结构介绍
要使用pandas,你首先就得熟悉它的两个主要数据结构:Series和DataFrame。虽然它们并不能解决所有问题,但它们为大多数应用提供了一种可靠的、易于使用的基础。
Series
Series是一种类似于一维数组的对象,它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的Series:
- In [4]: obj = Series([4, 7, -5, 3])
- In [5]: obj
- Out[5]:
- 0 4
- 1 7
- 2 -5
- 3 3
Series的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个0到N1(N为数据的长度)的整数型索引。你可以通过Series 的values和index属性获取其数组表示形式和索引对象:
- In [6]: obj.values
- Out[6]: array([ 4, 7, -5, 3])
- In [7]: obj.index
- Out[7]: Int64Index([0, 1, 2, 3])
通常,我们希望所创建的Series带有一个可以对各个数据点进行标记的索引:
- In [8]: obj2 = Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
- In [9]: obj2
- Out[9]:
- d 4
- b 7
- a -5
- c 3
- In [10]: obj2.index
- Out[10]: Index([d, b, a, c], dtype=object)
与普通NumPy数组相比,你可以通过索引的方式选取Series中的单个或一组值:
- In [11]: obj2['a']
- Out[11]: -5
- In [12]: obj2['d'] = 6
- In [13]: obj2[['c', 'a', 'd']]
- Out[13]:
- c 3
- a -5
- d 6
NumPy数组运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引和值之间的链接:
- In [14]: obj2
- Out[14]:
- d 6
- b 7
- a -5
- c 3
- In [15]: obj2[obj2 > 0] In [16]: obj2 * 2 In [17]: np.exp(obj2)
- Out[15]: Out[16]: Out[17]:
- d 6 d 12 d 403.428793
- b 7 b 14 b 1096.633158
- c 3 a -10 a 0.006738
- c 6 c 20.085537
还可以将Series看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中:
- In [18]: 'b' in obj2
- Out[18]: True
- In [19]: 'e' in obj2
- Out[19]: False
如果数据被存放在一个Python字典中,也可以直接通过这个字典来创建Series:
- In [20]: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
- In [21]: obj3 = Series(sdata)
- In [22]: obj3
- Out[22]:
- Ohio 35000
- Oregon 16000
- Texas 71000
- Utah 5000
如果只传入一个字典,则结果Series中的索引就是原字典的键(有序排列)。
- In [23]: states = ['California', 'Ohio', 'Oregon', 'Texas']
- In [24]: obj4 = Series(sdata, index=states)
- In [25]: obj4
- Out[25]:
- California NaN
- Ohio 35000
- Oregon 16000
- Texas 71000
在这个例子中,sdata中跟states索引相匹配的那3个值会被找出来并放到相应的位置上,但由于"California"所对应的sdata值找不到,所以其结果就为NaN(即“非数字”(not a number),在pandas中,它用于表示缺失或NA值)。我将使用缺失(missing)或NA表示缺失数据。pandas的isnull和notnull函数可用于检测缺失数据:
- In [26]: pd.isnull(obj4) In [27]: pd.notnull(obj4)
- Out[26]: Out[27]:
- California True California False
- Ohio False Ohio True
- Oregon False Oregon True
- Texas False Texas True
Series也有类似的实例方法:
- In [28]: obj4.isnull()
- Out[28]:
- California True
- Ohio False
- Oregon False
- Texas False
我将在本章详细讲解如何处理缺失数据。
对于许多应用而言,Series最重要的一个功能是:它在算术运算中会自动对齐不同索引的数据。
- In [29]: obj3 In [30]: obj4
- Out[29]: Out[30]:
- Ohio 35000 California NaN
- Oregon 16000 Ohio 35000
- Texas 71000 Oregon 16000
- Utah 5000 Texas 71000
- In [31]: obj3 + obj4
- Out[31]:
- California NaN
- Ohio 70000
- Oregon 32000
- Texas 142000
- Utah NaN
数据对齐功能将在一个单独的主题中讲解。
Series对象本身及其索引都有一个name属性,该属性跟pandas其他的关键功能关系非常密切:
- In [32]: obj4.name = 'population'
- In [33]: obj4.index.name = 'state'
- In [34]: obj4
- Out[34]:
- state
- California NaN
- Ohio 35000
- Oregon 16000
- Texas 71000
- Name: population
Series的索引可以通过赋值的方式就地修改:
- In [35]: obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan']
- In [36]: obj
- Out[36]:
- Bob 4
- Steve 7
- Jeff -5
- Ryan 3
DataFrame
DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,它可以被看做由Series组成的字典(共用同一个索引)。跟其他类似的数据结构相比(如R的data.frame),DataFrame中面向行和面向列的操作基本上是平衡的。其实,DataFrame中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。有关DataFrame内部的技术细节远远超出了本书所讨论的范围。
注意: 虽然DataFrame是以二维结构保存数据的,但你仍然可以轻松地将其表示为更高维度的数据(层次化索引的表格型结构,这是pandas中许多高级数据处理功能的关键要素,我们稍后再来讨论这个问题)。
构建DataFrame的办法有很多,最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典:
- data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
- 'year': [2000, 2001, 2002, 2001, 2002],
- 'pop': [1.5, 1.7, 3.6, 2.4, 2.9]}
- frame = DataFrame(data)
结果DataFrame会自动加上索引(跟Series一样),且全部列会被有序排列:
- In [38]: frame
- Out[38]:
- 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的列就会按照指定顺序进行排列:
- In [39]: DataFrame(data, columns=['year', 'state', 'pop'])
- Out[39]:
- 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
跟Series一样,如果传入的列在数据中找不到,就会产生NA值:
- In [40]: frame2 = DataFrame(data, columns=['year', 'state', 'pop', 'debt'],
- ...: index=['one', 'two', 'three', 'four', 'five'])
- In [41]: frame2
- Out[41]:
- 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
- In [42]: frame2.columns
- Out[42]: Index([year, state, pop, debt], dtype=object)
通过类似字典标记的方式或属性的方式,可以将DataFrame的列获取为一个Series:
- In [43]: frame2['state'] In [44]: frame2.year
- Out[43]: Out[44]:
- one Ohio one 2000
- two Ohio two 2001
- three Ohio three 2002
- four Nevada four 2001
- five Nevada five 2002
- Name: state Name: year
注意,返回的Series拥有原DataFrame相同的索引,且其name属性也已经被相应地设置好了。行也可以通过位置或名称的方式进行获取,比如用索引字段ix(稍后将对此进行详细讲解):
- In [45]: frame2.ix['three']
- Out[45]:
- year 2002
- state Ohio
- pop 3.6
- debt NaN
- Name: three
列可以通过赋值的方式进行修改。例如,我们可以给那个空的"debt"列赋上一个标量值或一组值:
- In [46]: frame2['debt'] = 16.5
- In [47]: frame2
- Out[47]:
- 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
- In [48]: frame2['debt'] = np.arange(5.)
- In [49]: frame2
- Out[49]:
- year state pop debt
- one 2000 Ohio 1.5 0
- two 2001 Ohio 1.7 1
- three 2002 Ohio 3.6 2
- four 2001 Nevada 2.4 3
- five 2002 Nevada 2.9 4
将列表或数组赋值给某个列时,其长度必须跟DataFrame的长度相匹配。如果赋值的是一个Series,就会精确匹配DataFrame的索引,所有的空位都将被填上缺失值:
- In [50]: val = Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])
- In [51]: frame2['debt'] = val
- In [52]: frame2
- Out[52]:
- 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
为不存在的列赋值会创建出一个新列。关键字del用于删除列:
- In [53]: frame2['eastern'] = frame2.state == 'Ohio'
- In [54]: frame2
- Out[54]:
- 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
- In [55]: del frame2['eastern']
- In [56]: frame2.columns
- Out[56]: Index([year, state, pop, debt], dtype=object)
警告: 通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的Series所做的任何就地修改全都会反映到源DataFrame上。通过Series的copy方法即可显式地复制列。
另一种常见的数据形式是嵌套字典(也就是字典的字典):
- In [57]: pop = {'Nevada': {2001: 2.4, 2002: 2.9},
- ....: 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
如果将它传给DataFrame,它就会被解释为:外层字典的键作为列,内层键则作为行索引:
- In [58]: frame3 = DataFrame(pop)
- In [59]: frame3
- Out[59]:
- Nevada Ohio
- 2000 NaN 1.5
- 2001 2.4 1.7
- 2002 2.9 3.6
当然,你也可以对该结果进行转置:
- In [60]: frame3.T
- Out[60]:
- 2000 2001 2002
- Nevada NaN 2.4 2.9
- Ohio 1.5 1.7 3.6
内层字典的键会被合并、排序以形成最终的索引。如果显式指定了索引,则不会这样:
- In [61]: DataFrame(pop, index=[2001, 2002, 2003])
- Out[61]:
- Nevada Ohio
- 2001 2.4 1.7
- 2002 2.9 3.6
- 2003 NaN NaN
由Series组成的字典差不多也是一样的用法:
- In [62]: pdata = {'Ohio': frame3['Ohio'][:-1],
- ....: 'Nevada': frame3['Nevada'][:2]}
- In [63]: DataFrame(pdata)
- Out[63]:
- Nevada Ohio
- 2000 NaN 1.5
- 2001 2.4 1.7
表5-1列出了DataFrame构造函数所能接受的各种数据。
如果设置了DataFrame的index和columns的name属性,则这些信息也会被显示出来:
- In [64]: frame3.index.name = 'year'; frame3.columns.name = 'state'
- In [65]: frame3
- Out[65]:
- state Nevada Ohio
- year
- 2000 NaN 1.5
- 2001 2.4 1.7
- 2002 2.9 3.6
跟Series一样,values属性也会以二维ndarray的形式返回DataFrame中的数据:
- In [66]: frame3.values
- Out[66]:
- array([[ nan, 1.5],
- [ 2.4, 1.7],
- [ 2.9, 3.6]])
如果DataFrame各列的数据类型不同,则值数组的数据类型就会选用能兼容所有列的数据类型:
- In [67]: frame2.values
- Out[67]:
- array([[2000, Ohio, 1.5, nan],
- [2001, Ohio, 1.7, -1.2],
- [2002, Ohio, 3.6, nan],
- [2001, Nevada, 2.4, -1.5],
- [2002, Nevada, 2.9, -1.7]], dtype=object)
索引对象
pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index:
- In [68]: obj = Series(range(3), index=['a', 'b', 'c'])
- In [69]: index = obj.index
- In [70]: index
- Out[70]: Index([a, b, c], dtype=object)
- In [71]: index[1:]
- Out[71]: Index([b, c], dtype=object)
Index对象是不可修改的(immutable),因此用户不能对其进行修改:
- In [72]: index[1] = 'd'
- In [72]: index[1] = 'd'
Exception Traceback (most recent call last) <ipython-input-72-676fdeb26a68> in <module>() ——> 1 index[1] = 'd'
/Users/wesm/code/pandas/pandas/core/index.pyc in setitem (self, key, value) 302 def setitem(self, key, value): 303 """Disable the setting of values.""" —> 304 raise Exception(str(self.class) + ' object is immutable') 305 306 def getitem(self, key):
Exception: <class 'pandas.core.index.Index'> object is immutable
不可修改性非常重要,因为这样才能使Index对象在多个数据结构之间安全共享:
- In [73]: index = pd.Index(np.arange(3))
- In [74]: obj2 = Series([1.5, -2.5, 0], index=index)
- In [75]: obj2.index is index
- Out[75]: True
表5-2列出了pandas库中内置的Index类。由于开发人员的不懈努力,Index甚至可以被继承从而实现特别的轴索引功能。
注意: 虽然大部分用户都不需要知道太多关于Index对象的细节,但它们确实是pandas数据模型的重要组成部分。
除了长得像数组,Index的功能也类似一个固定大小的集合:
- In [76]: frame3
- Out[76]:
- state Nevada Ohio
- year
- 2000 NaN 1.5
- 2001 2.4 1.7
- 2002 2.9 3.6
- In [77]: 'Ohio' in frame3.columns
- Out[77]: True
- In [78]: 2003 in frame3.index
- Out[78]: False
每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。表5-3列出了这些函数。