- 5.1 pandas的数据结构介绍
- Series
- DataFrame
- 索引对象
5.1 pandas的数据结构介绍
要使用pandas,你首先就得熟悉它的两个主要数据结构:Series和DataFrame。虽然它们并不能解决所有问题,但它们为大多数应用提供了一种可靠的、易于使用的基础。
Series
Series是一种类似于一维数组的对象,它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的Series:
In [11]: obj = pd.Series([4, 7, -5, 3])In [12]: objOut[12]:0 41 72 -53 3dtype: int64
Series的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个0到N-1(N为数据的长度)的整数型索引。你可以通过Series 的values和index属性获取其数组表示形式和索引对象:
In [13]: obj.valuesOut[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]: obj2Out[16]:d 4b 7a -5c 3dtype: int64In [17]: obj2.indexOut[17]: Index(['d', 'b', 'a', 'c'], dtype='object')
与普通NumPy数组相比,你可以通过索引的方式选取Series中的单个或一组值:
In [18]: obj2['a']Out[18]: -5In [19]: obj2['d'] = 6In [20]: obj2[['c', 'a', 'd']]Out[20]:c 3a -5d 6dtype: int64
[‘c’, ‘a’, ‘d’]是索引列表,即使它包含的是字符串而不是整数。
使用NumPy函数或类似NumPy的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值的链接:
In [21]: obj2[obj2 > 0]Out[21]:d 6b 7c 3dtype: int64In [22]: obj2 * 2Out[22]:d 12b 14a -10c 6dtype: int64In [23]: np.exp(obj2)Out[23]:d 403.428793b 1096.633158a 0.006738c 20.085537dtype: float64
还可以将Series看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中:
In [24]: 'b' in obj2Out[24]: TrueIn [25]: 'e' in obj2Out[25]: False
如果数据被存放在一个Python字典中,也可以直接通过这个字典来创建Series:
In [26]: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}In [27]: obj3 = pd.Series(sdata)In [28]: obj3Out[28]:Ohio 35000Oregon 16000Texas 71000Utah 5000dtype: int64
如果只传入一个字典,则结果Series中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序:
In [29]: states = ['California', 'Ohio', 'Oregon', 'Texas']In [30]: obj4 = pd.Series(sdata, index=states)In [31]: obj4Out[31]:California NaNOhio 35000.0Oregon 16000.0Texas 71000.0dtype: float64
在这个例子中,sdata中跟states索引相匹配的那3个值会被找出来并放到相应的位置上,但由于”California”所对应的sdata值找不到,所以其结果就为NaN(即“非数字”(not a number),在pandas中,它用于表示缺失或NA值)。因为‘Utah’不在states中,它被从结果中除去。
我将使用缺失(missing)或NA表示缺失数据。pandas的isnull和notnull函数可用于检测缺失数据:
In [32]: pd.isnull(obj4)Out[32]:California TrueOhio FalseOregon FalseTexas Falsedtype: boolIn [33]: pd.notnull(obj4)Out[33]:California FalseOhio TrueOregon TrueTexas Truedtype: bool
Series也有类似的实例方法:
In [34]: obj4.isnull()Out[34]:California TrueOhio FalseOregon FalseTexas Falsedtype: bool
我将在第7章详细讲解如何处理缺失数据。
对于许多应用而言,Series最重要的一个功能是,它会根据运算的索引标签自动对齐数据:
In [35]: obj3Out[35]:Ohio 35000Oregon 16000Texas 71000Utah 5000dtype: int64In [36]: obj4Out[36]:California NaNOhio 35000.0Oregon 16000.0Texas 71000.0dtype: float64In [37]: obj3 + obj4Out[37]:California NaNOhio 70000.0Oregon 32000.0Texas 142000.0Utah NaNdtype: float64
数据对齐功能将在后面详细讲解。如果你使用过数据库,你可以认为是类似join的操作。
Series对象本身及其索引都有一个name属性,该属性跟pandas其他的关键功能关系非常密切:
In [38]: obj4.name = 'population'In [39]: obj4.index.name = 'state'In [40]: obj4Out[40]:stateCalifornia NaNOhio 35000.0Oregon 16000.0Texas 71000.0Name: population, dtype: float64
Series的索引可以通过赋值的方式就地修改:
In [41]: objOut[41]:0 41 72 -53 3dtype: int64In [42]: obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan']In [43]: objOut[43]:Bob 4Steve 7Jeff -5Ryan 3dtype: 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)
结果DataFrame会自动加上索引(跟Series一样),且全部列会被有序排列:
In [45]: frameOut[45]:pop state year0 1.5 Ohio 20001 1.7 Ohio 20012 3.6 Ohio 20023 2.4 Nevada 20014 2.9 Nevada 20025 3.2 Nevada 2003
如果你使用的是Jupyter notebook,pandas DataFrame对象会以对浏览器友好的HTML表格的方式呈现。
对于特别大的DataFrame,head方法会选取前五行:
In [46]: frame.head()Out[46]:pop state year0 1.5 Ohio 20001 1.7 Ohio 20012 3.6 Ohio 20023 2.4 Nevada 20014 2.9 Nevada 2002
如果指定了列序列,则DataFrame的列就会按照指定顺序进行排列:
In [47]: pd.DataFrame(data, columns=['year', 'state', 'pop'])Out[47]:year state pop0 2000 Ohio 1.51 2001 Ohio 1.72 2002 Ohio 3.63 2001 Nevada 2.44 2002 Nevada 2.95 2003 Nevada 3.2
如果传入的列在数据中找不到,就会在结果中产生缺失值:
In [48]: frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'],....: index=['one', 'two', 'three', 'four',....: 'five', 'six'])In [49]: frame2Out[49]:year state pop debtone 2000 Ohio 1.5 NaNtwo 2001 Ohio 1.7 NaNthree 2002 Ohio 3.6 NaNfour 2001 Nevada 2.4 NaNfive 2002 Nevada 2.9 NaNsix 2003 Nevada 3.2 NaNIn [50]: frame2.columnsOut[50]: Index(['year', 'state', 'pop', 'debt'], dtype='object')
通过类似字典标记的方式或属性的方式,可以将DataFrame的列获取为一个Series:
In [51]: frame2['state']Out[51]:one Ohiotwo Ohiothree Ohiofour Nevadafive Nevadasix NevadaName: state, dtype: objectIn [52]: frame2.yearOut[52]:one 2000two 2001three 2002four 2001five 2002six 2003Name: year, dtype: int64
笔记:IPython提供了类似属性的访问(即frame2.year)和tab补全。
frame2[column]适用于任何列的名,但是frame2.column只有在列名是一个合理的Python变量名时才适用。
注意,返回的Series拥有原DataFrame相同的索引,且其name属性也已经被相应地设置好了。
行也可以通过位置或名称的方式进行获取,比如用loc属性(稍后将对此进行详细讲解):
In [53]: frame2.loc['three']Out[53]:year 2002state Ohiopop 3.6debt NaNName: three, dtype: object
列可以通过赋值的方式进行修改。例如,我们可以给那个空的”debt”列赋上一个标量值或一组值:
In [54]: frame2['debt'] = 16.5In [55]: frame2Out[55]:year state pop debtone 2000 Ohio 1.5 16.5two 2001 Ohio 1.7 16.5three 2002 Ohio 3.6 16.5four 2001 Nevada 2.4 16.5five 2002 Nevada 2.9 16.5six 2003 Nevada 3.2 16.5In [56]: frame2['debt'] = np.arange(6.)In [57]: frame2Out[57]:year state pop debtone 2000 Ohio 1.5 0.0two 2001 Ohio 1.7 1.0three 2002 Ohio 3.6 2.0four 2001 Nevada 2.4 3.0five 2002 Nevada 2.9 4.0six 2003 Nevada 3.2 5.0
将列表或数组赋值给某个列时,其长度必须跟DataFrame的长度相匹配。如果赋值的是一个Series,就会精确匹配DataFrame的索引,所有的空位都将被填上缺失值:
In [58]: val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])In [59]: frame2['debt'] = valIn [60]: frame2Out[60]:year state pop debtone 2000 Ohio 1.5 NaNtwo 2001 Ohio 1.7 -1.2three 2002 Ohio 3.6 NaNfour 2001 Nevada 2.4 -1.5five 2002 Nevada 2.9 -1.7six 2003 Nevada 3.2 NaN
为不存在的列赋值会创建出一个新列。关键字del用于删除列。
作为del的例子,我先添加一个新的布尔值的列,state是否为’Ohio’:
In [61]: frame2['eastern'] = frame2.state == 'Ohio'In [62]: frame2Out[62]:year state pop debt easternone 2000 Ohio 1.5 NaN Truetwo 2001 Ohio 1.7 -1.2 Truethree 2002 Ohio 3.6 NaN Truefour 2001 Nevada 2.4 -1.5 Falsefive 2002 Nevada 2.9 -1.7 Falsesix 2003 Nevada 3.2 NaN False
注意:不能用frame2.eastern创建新的列。
del方法可以用来删除这列:
In [63]: del frame2['eastern']In [64]: frame2.columnsOut[64]: Index(['year', 'state', 'pop', 'debt'], dtype='object')
注意:通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的Series所做的任何就地修改全都会反映到源DataFrame上。通过Series的copy方法即可指定复制列。
另一种常见的数据形式是嵌套字典:
In [65]: pop = {'Nevada': {2001: 2.4, 2002: 2.9},....: 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
如果嵌套字典传给DataFrame,pandas就会被解释为:外层字典的键作为列,内层键则作为行索引:
In [66]: frame3 = pd.DataFrame(pop)In [67]: frame3Out[67]:Nevada Ohio2000 NaN 1.52001 2.4 1.72002 2.9 3.6
你也可以使用类似NumPy数组的方法,对DataFrame进行转置(交换行和列):
In [68]: frame3.TOut[68]:2000 2001 2002Nevada NaN 2.4 2.9Ohio 1.5 1.7 3.6
内层字典的键会被合并、排序以形成最终的索引。如果明确指定了索引,则不会这样:
In [69]: pd.DataFrame(pop, index=[2001, 2002, 2003])Out[69]:Nevada Ohio2001 2.4 1.72002 2.9 3.62003 NaN NaN
由Series组成的字典差不多也是一样的用法:
In [70]: pdata = {'Ohio': frame3['Ohio'][:-1],....: 'Nevada': frame3['Nevada'][:2]}In [71]: pd.DataFrame(pdata)Out[71]:Nevada Ohio2000 NaN 1.52001 2.4 1.7
表5-1列出了DataFrame构造函数所能接受的各种数据。

如果设置了DataFrame的index和columns的name属性,则这些信息也会被显示出来:
In [72]: frame3.index.name = 'year'; frame3.columns.name = 'state'In [73]: frame3Out[73]:state Nevada Ohioyear2000 NaN 1.52001 2.4 1.72002 2.9 3.6
跟Series一样,values属性也会以二维ndarray的形式返回DataFrame中的数据:
In [74]: frame3.valuesOut[74]:array([[ nan, 1.5],[ 2.4, 1.7],[ 2.9, 3.6]])
如果DataFrame各列的数据类型不同,则值数组的dtype就会选用能兼容所有列的数据类型:
In [75]: frame2.valuesOut[75]: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],[2003, 'Nevada', 3.2, nan]], dtype=object)
索引对象
pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index:
In [76]: obj = pd.Series(range(3), index=['a', 'b', 'c'])In [77]: index = obj.indexIn [78]: indexOut[78]: Index(['a', 'b', 'c'], dtype='object')In [79]: index[1:]Out[79]: Index(['b', 'c'], dtype='object')
Index对象是不可变的,因此用户不能对其进行修改:
index[1] = 'd' # TypeError
不可变可以使Index对象在多个数据结构之间安全共享:
In [80]: labels = pd.Index(np.arange(3))In [81]: labelsOut[81]: Int64Index([0, 1, 2], dtype='int64')In [82]: obj2 = pd.Series([1.5, -2.5, 0], index=labels)In [83]: obj2Out[83]:0 1.51 -2.52 0.0dtype: float64In [84]: obj2.index is labelsOut[84]: True
注意:虽然用户不需要经常使用Index的功能,但是因为一些操作会生成包含被索引化的数据,理解它们的工作原理是很重要的。
除了类似于数组,Index的功能也类似一个固定大小的集合:
In [85]: frame3Out[85]:state Nevada Ohioyear2000 NaN 1.52001 2.4 1.72002 2.9 3.6In [86]: frame3.columnsOut[86]: Index(['Nevada', 'Ohio'], dtype='object', name='state')In [87]: 'Ohio' in frame3.columnsOut[87]: TrueIn [88]: 2003 in frame3.indexOut[88]: False
与python的集合不同,pandas的Index可以包含重复的标签:
In [89]: dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar'])In [90]: dup_labelsOut[90]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object')
选择重复的标签,会显示所有的结果。
每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。表5-2列出了这些函数。

