5.层次化索引
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5.层次化索引相关的知识,希望对你有一定的参考价值。
参考技术A 到目前为止我们关注的是保存在Pandas Series和DataFrame中的一维和二维数据。通常超越二维的数据,即用两个以上键值索引的数据也是很有用的。
尽管Pandas提供了“Panel”和“Panel4D”对象来本地处理3维和4维数据,实践中更常用的方式是在单个索引中使用层次化索引(也叫多级索引)来包含多个索引。
以这种方式,高维数据可以被紧凑的表示在我们熟悉的一维Series和二维DataFrame对象中。
在这部分,我们将探索直接创建多级索引对象,仔细考虑对多级索引数据的索引,切片和计算统计,还会介绍对数据进行简单和层次化索引转换有用的函数。
我们以标准的导入开始:
让我们由考虑如果使用一维Series表达二维数据开始。具体讲,我们考虑那种带有字母和数字键值数据的Series。
假设你想要追踪两个不同年份的州数据。使用我们已经讲过的Pandas工具,你可能首先想到的是简单的使用Python元组作为键值:
使用这种索引方案,你可以直接基于多索引进行Series的索引和切片操作:
但是便利也就到此为止了。例如,如果你需要选取所有2010年的值,你需要使用混乱(有可能缓慢的)方法才能实现:
这种方法得到了期待的结果,但它并不像我们喜爱的Pandas切片操作那样简洁(在数据很大时,效率也不高)。
幸运的是,Pandas提供了一种更好的方法。我们基于元组的索引本质上来说是初级的多索引,Pandas的多级索引类型带来我们所希望的操作类型。我们可以像下面这样通过元组创建多索引:
注意MultiIndex包含多个索引级别,在这个例子中,有州名称和年份,以及多个为每个数据点都记录层级的标签。
这里Series结果的头两列显示的是多索引值,第三行显示的是数据。注意第一列里面一些条目是空的:在多索引表达是,空位表示那里面的值与上一行的值一样。
现在访问第二个索引是2010的所有数据,我们就可以简单的使用Pandas切片记号:
结果是带有我们想要键值的单索引数组。跟我们开始时自制的基于元组的解决方法相比,现在的语法更简洁(执行效率也更高)。我们将会更深入的讨论这类在层级索引数据上面索引操作。
这里你也许会注意到一点别的东西:我们可以很容易的使用带有索引和列标签的DataFrame来存储同样的数据。实际上,Pandas在设计时也想到了这种对等性。unstack()方法会快速的将多层索引Series转换成常用的DataFrame:
自然而然,stack()方法提供了相反的操作:
看到这,您可能奇怪为什么我们还要麻烦的使用层级索引。原因很简单:就如我们可以用一维Series的多层级索引来表示二维数据一样,我们也能使用Series或DataFrame来表示三维或多维数据。多级索引中每个额外的层级代表数据的一个维度;这个属性为我们可以表示的数据类型带来许多灵活性。具体来讲,我们想要添加一列来表示每年的人口统计数据(比如,小于18岁的人口数);使用多级索引,就是简单的在DataFrame中添加一列:
另外,所有ufuncs和其他在Operating on Data in Pandas 讨论的功能都能在层次化索引上工作的很好。我们用上面的数据,来计算每年低于18岁的人口比例:
这使我们可以方便和快捷操纵高维数据。
为Series和DataFrame创建多级索引最直接的方法就是传递两个或多个索引数组给构造器。例如:
创建索引的工作由后台去做。
类似的,如果你传递一个带有适当元组作为键值的字典的话,Pandas将会自动识别并且使用多级索引:
然而,有时候明确的创建多级索引也是很有用的;我们来看几个用法。
如何构造索引有更多的灵活性,你可以使用类构造方法pd.MultiIndex。例如,如我们之前做的,可以通过给出了各级索引值的数组列表来构建多级索引:
也可以通过已经给出每个点多级索引值的元组列表来构建:
甚至可通过单索引的笛卡尔积来创建:
同样的,可以直接通过传递内部编码levels(包含每级可用索引值的列表)和lables(指代这些标签的列表)来构造多级索引:
所有这些对象,在创建Series或DataFrame时,都可用作为index参数传进去,或者传递给已经存在了的Series或DataFrame对象的reindex方法。
有时,给多级索引的层级命名时很有用的。命名可以通过names参数给MultiIndex构造器来实现,或者设置已有索引的names属性:
随着参与的数据集变多,给索引命名来记录不同索引的意义是非常有用的。
在DataFrame中,行和列是完全对称的,正如行有多级索引,列也可以有多级索引。考虑如下模拟的医疗数据:
我们很容易的得到了行和列的多级索引。这基本上是四维数据,访问对象,检查类型,年份和访问次数。有了这个,我们可以通过最上层人的名称来检索,并且能够得到只包含那个人信息的完整DataFrame:
对于包含多个标签涵盖多次,多个主题(人口,国家,城市等)的复杂数据记录,使用层级化的行和列索引将会极其方便!
多索引上面的检索和切片被设计的很直观,把索引当作是增加的维度将会很有帮助。
我们首先来看Series上的多索引检索,然后再看DataFrame上的。
考虑我们之前看到的州人口的多索引Series:
借助于多条目索引,我们可以访问单个数据元素:
MultiIndex也支持部分索引,或者只是检索索引层级中的一个。结果是另一个Series,保留着较低层的索引。
部分切片也是可用的,只要多级索引是排过序的(参见 Sorted and Unsorted Indices ):
对于排过序的索引,将前级索引置为空,基于低层级索引的检索操作也可以执行:
其他类型的检索和筛选操作(见 Data Indexing and Selection ) 工作的也很好;例如,基于布尔过滤筛选:
基于花式索引的筛选也可以工作:
DataFrame的多层索引的行为与Series类似。考虑前面用到的医疗数据DataFrame:
要记住列在DataFrame中是主要元素,用于Series的多级索引语法也适用于列。例如我们可以使用简单的方式获得Guido的心率:
同样,与单一索引情况一样,我们可以使用loc,iloc和ix 见 Data Indexing and Selection 。例如:
这些检索器在二维数据上需要提供数组类似输入,但可以把多索引元组传递给loc和iloc。例如:
在索引元组中使用切片不是特别方便;尝试在元组中使用切片将会导致语法错误:
可以通过显示使用Python内置的slice()函数构建期望的切片来绕过上面的限制。但更好的办法是使用IndexSlice对象,它是Pandas专门用来处理这种情形的。例如:
有许多方法可以同多索引Series和DataFrame进行交互,如同本书中的许多工具一样,最好的熟悉方法是多尝试!
使用多索引数据的一个关键是知道如何有效的转换数据。有许多操作会保留数据集的所有信息,但为了不同的目的而对它进行重拍。我们看过简短的例子:stack()和unstack()方法;但是有更多方法可以精细的控制数据在层级索引和列直接进行转换,让我们来探索它们:
之前我们曾经有警告,但在这里应该强调一下.如果索引是未排序的话,许多多索引切片操作将会失败。
我们由创建一些简单的未排序多索引数据开始:
如果对这个索引进行部分切片的话,它将导致一个错误:
尽管错误信息不是特别清楚,原因是多级索引没有排序。因为各种原因,部分切片和其它类似操作要求多级索引的各个层级是排序了的。Pandas提供了几种方法的函数来执行这类排序;例如DataFrame的sort_index()和sortlevel()方法。这儿我们使用最简单的sort_index():
索引经过这样的排序,部分切片就会按期望的工作了:
我们前面简要的见过,可以将聚集的多索引转换未简单的二维表现,所使用的层级是可以选的:
unstack()的反向操作是stack(),它可以用来恢复原始的Series:
另一种重排层级化数据的方法是将索引标签变成列;这可以通过reset_index方法实现。在人口字典上调用这个方法会导致原来index里面的state和year信息变成DataFrame对应的列。为清晰起见,我们可以指定数据列显示的名称:
通常在现实世界中,原始的输入数据就是这个样子的。通过列名称来构建多索引非常有用。可以同DataFrame的set_index方法来实现,结果返回的就是多级索引DataFrame:
在实践中,我发现这类重置索引的方法是处理真实数据集最有用的模式之一。
我们之前看到过Pandas的内部数据聚合方法,例如mean(),sum()和max()。对于层级索引数据,可以传递一个level参数来控制对那个数据子集进行计算。例如,我们回到健康数据:
也许我们想平均一下每年两次来访的测量值。我们可以通过指定想要的索引层级名称来实现,本例使用的是year:
借助于使用关键字axis,我们也可以获取列中某层级的均值:
只用了两行,我们就能够发现访问对象的每年平均心率和体温测量值。这个语法实际是Groupby功能的快捷方法,见 Aggregation and Grouping
虽然这只是一个小例子,许多真实的数据集由相似的层级结构。
Pandas还有几种我们没有讲到的数据类型,即pd.Panel和pd.Panel4D对象。它们分别可以被看做是泛化的3维和4维结构,就像Serie是一维,DataFrame是二维一样。一旦熟悉了Series和DataFrame的索引和数据操作,对Panel和Panel4D基本可以直接使用。特别是索引器ix,loc和iloc,它们完全适用于这些高维数据结构。
我们不会再覆盖这些面板结构,因为我发现在大多数情况下,多级索引是非常有用的,并且可以在概念上非常简洁的表示高维数据。另外面板数据是密数据表达,而多级索引基本上是稀数据表达。随着维度的增加,密表达方式对于真实数据集来说变得效率很低。但对于一些特殊的应用,这些结构也很有用。如果想要知道更多关于Panel和Panel4D结构,请看列在 Further Resources .中的参考文献。
数据规整:聚合合并和重塑
目录
一、层次化索引
层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个Series,并用一个由列表或数组组成的列表作为索引:
In [9]: data = pd.Series(np.random.randn(9),
...: index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'],
...: [1, 2, 3, 1, 3, 1, 2, 2, 3]])
In [10]: data
Out[10]:
a 1 -0.204708
2 0.478943
3 -0.519439
b 1 -0.555730
3 1.965781
c 1 1.393406
2 0.092908
d 2 0.281746
3 0.769023
dtype: float64
看到的结果是经过美化的带有MultiIndex索引的Series的格式。索引之间的“间隔”表示“直接使用上面的标签”:
In [11]: data.index
Out[11]:
MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])
对于一个层次化索引的对象,可以使用所谓的部分索引,使用它选取数据子集的操作更简单:
In [12]: data['b']
Out[12]:
1 -0.555730
3 1.965781
dtype: float64
In [13]: data['b':'c']
Out[13]:
b 1 -0.555730
3 1.965781
c 1 1.393406
2 0.092908
dtype: float64
In [14]: data.loc[['b', 'd']]
Out[14]:
b 1 -0.555730
3 1.965781
d 2 0.281746
3 0.769023
dtype: float64
有时甚至还可以在“内层”中进行选取
In [15]: data.loc[:, 2]
Out[15]:
a 0.478943
c 0.092908
d 0.281746
dtype: float64
对于一个DataFrame,每条轴都可以有分层索引:
In [18]: frame = pd.DataFrame(np.arange(12).reshape((4, 3)),
....: index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
....: columns=[['Ohio', 'Ohio', 'Colorado'],
....: ['Green', 'Red', 'Green']])
In [19]: frame
Out[19]:
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中:
In [20]: frame.index.names = ['key1', 'key2']
In [21]: frame.columns.names = ['state', 'color']
In [22]: frame
Out[22]:
state Ohio Colorado
color Green Red Green
key1 key2
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
重排与分级排序
有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进行排序。swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化):
In [24]: frame.swaplevel('key1', 'key2')
Out[24]:
state Ohio Colorado
color Green Red Green
key2 key1
1 a 0 1 2
2 a 3 4 5
1 b 6 7 8
2 b 9 10 11
而sort_index则根据单个级别中的值对数据进行排序。交换级别时,常常也会用到sort_index,这样最终结果就是按照指定顺序进行字母排序了:
In [25]: frame.sort_index(level=1)
Out[25]:
state Ohio Colorado
color Green Red Green
key1 key2
a 1 0 1 2
b 1 6 7 8
a 2 3 4 5
b 2 9 10 11
In [26]: frame.swaplevel(0, 1).sort_index(level=0)
Out[26]:
state Ohio Colorado
color Green Red Green
key2 key1
1 a 0 1 2
b 6 7 8
2 a 3 4 5
b 9 10 11
根据级别汇总统计
许多对DataFrame和Series的描述和汇总统计都有一个level选项,它用于指定在某条轴上求和的级别。再以上面那个DataFrame为例,我们可以根据行或列上的级别来进行求和:
In [27]: frame.sum(level='key2')
Out[27]:
state Ohio Colorado
color Green Red Green
key2
1 6 8 10
2 12 14 16
In [28]: frame.sum(level='color', axis=1)
Out[28]:
color Green Red
key1 key2
a 1 2 1
2 8 4
b 1 14 7
2 20 10
DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame:
In [29]: frame = pd.DataFrame('a': range(7), 'b': range(7, 0, -1),
....: 'c': ['one', 'one', 'one', 'two', 'two',
....: 'two', 'two'],
....: 'd': [0, 1, 2, 0, 1, 2, 3])
In [30]: frame
Out[30]:
a b c d
0 0 7 one 0
1 1 6 one 1
2 2 5 one 2
3 3 4 two 0
4 4 3 two 1
5 5 2 two 2
6 6 1 two 3
默认情况下,那些列会从DataFrame中移除,但也可以将其保留下来:
In [33]: frame.set_index(['c', 'd'], drop=False)
Out[33]:
a b c d
c d
one 0 0 7 one 0
1 1 6 one 1
2 2 5 one 2
two 0 3 4 two 0
1 4 3 two 1
2 5 2 two 2
3 6 1 two 3
reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面:
In [34]: frame2.reset_index()
Out[34]:
c d a b
0 one 0 0 7
1 one 1 1 6
2 one 2 2 5
3 two 0 3 4
4 two 1 4 3
5 two 2 5 2
6 two 3 6 1
二、合并数据集
pandas对象中的数据可以通过一些方式进行合并:
- pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。SQL或其他关系型数据库的用户对此应该会比较熟悉,因为它实现的就是数据库的join操作。
- pandas.concat可以沿着一条轴将多个对象堆叠到一起。
- 实例方法combine_first可以将重复数据拼接在一起,用一个对象中的值填充另一个对象中的缺失值。
我将分别对它们进行讲解,并给出一些例子。本书剩余部分的示例中将经常用到它们。
数据库风格的DataFrame合并
数据集的合并(merge)或连接(join)运算是通过一个或多个键将行连接起来的。这些运算是关系型数据库(基于SQL)的核心。pandas的merge函数是对数据应用这些算法的主要切入点。
以一个简单的例子开始:
In [35]: df1 = pd.DataFrame('key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
....: 'data1': range(7))
In [36]: df2 = pd.DataFrame('key': ['a', 'b', 'd'],
....: 'data2': range(3))
In [37]: df1
Out[37]:
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 a
6 6 b
In [38]: df2
Out[38]:
data2 key
0 0 a
1 1 b
2 2 d
这是一种多对一的合并。df1中的数据有多个被标记为a和b的行,而df2中key列的每个值则仅对应一行。对这些对象调用merge即可得到:
In [39]: pd.merge(df1, df2)
Out[39]:
data1 key data2
0 0 b 1
1 1 b 1
2 6 b 1
3 2 a 0
4 4 a 0
5 5 a 0
注意,我并没有指明要用哪个列进行连接。如果没有指定,merge就会将重叠列的列名当做键。不过,最好明确指定一下:
In [40]: pd.merge(df1, df2, on='key')
Out[40]:
data1 key data2
0 0 b 1
1 1 b 1
2 6 b 1
3 2 a 0
4 4 a 0
5 5 a 0
如果两个对象的列名不同,也可以分别进行指定:
In [41]: df3 = pd.DataFrame('lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
....: 'data1': range(7))
In [42]: df4 = pd.DataFrame('rkey': ['a', 'b', 'd'],
....: 'data2': range(3))
In [43]: pd.merge(df3, df4, left_on='lkey', right_on='rkey')
Out[43]:
data1 lkey data2 rkey
0 0 b 1 b
1 1 b 1 b
2 6 b 1 b
3 2 a 0 a
4 4 a 0 a
5 5 a 0 a
可能你已经注意到了,结果里面c和d以及与之相关的数据消失了。默认情况下,merge做的是“内连接”;结果中的键是交集。其他方式还有"left"、“right"以及"outer”。外连接求取的是键的并集,组合了左连接和右连接的效果:
In [44]: pd.merge(df1, df2, how='outer')
Out[44]:
data1 key data2
0 0.0 b 1.0
1 1.0 b 1.0
2 6.0 b 1.0
3 2.0 a 0.0
4 4.0 a 0.0
5 5.0 a 0.0
6 3.0 c NaN
7 NaN d 2.0
多键合并
In [51]: left = pd.DataFrame('key1': ['foo', 'foo', 'bar'],
....: 'key2': ['one', 'two', 'one'],
....: 'lval': [1, 2, 3])
In [52]: right = pd.DataFrame('key1': ['foo', 'foo', 'bar', 'bar'],
....: 'key2': ['one', 'one', 'one', 'two'],
....: 'rval': [4, 5, 6, 7])
In [53]: pd.merge(left, right, on=['key1', 'key2'], how='outer')
Out[53]:
key1 key2 lval rval
0 foo one 1.0 4.0
1 foo one 1.0 5.0
2 foo two 2.0 NaN
3 bar one 3.0 6.0
4 bar two NaN 7.0
对于合并运算需要考虑的最后一个问题是对重复列名的处理。虽然你可以手工处理列名重叠的问题(查看前面介绍的重命名轴标签),但merge有一个更实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串:
In [54]: pd.merge(left, right, on='key1')
Out[54]:
key1 key2_x lval key2_y rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
In [55]: pd.merge(left, right, on='key1', suffixes=('_left', '_right'))
Out[55]:
key1 key2_left lval key2_right rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
索引上的合并
有时候,DataFrame中的连接键位于其索引中。在这种情况下,你可以传入left_index=True或right_index=True(或两个都传)以说明索引应该被用作连接键:
In [56]: left1 = pd.DataFrame('key': ['a', 'b', 'a', 'a', 'b', 'c'],
....: 'value': range(6))
In [57]: right1 = pd.DataFrame('group_val': [3.5, 7], index=['a', 'b'])
In [58]: left1
Out[58]:
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
In [59]: right1
Out[59]:
group_val
a 3.5
b 7.0
In [60]: pd.merge(left1, right1, left_on='key', right_index=True)
Out[60]:
key value group_val
0 a 0 3.5
以上是关于5.层次化索引的主要内容,如果未能解决你的问题,请参考以下文章