层级索引¶
import pandas as pd
import numpy as np
多级索引 Series¶
一种笨办法¶
如果你想分析美国各个州在不同年份的数据,你可能用一个Python元组来定义索引:
index = [('California', 2000), ('California', 2010),
('New York', 2000), ('New York', 2010),
('Texas', 2000), ('Texas', 2010)]
populations = [33871648, 37253956,
18976457, 19378102,
20851820, 25145561]
pop = pd.Series(populations, index=index)
pop
(California, 2000) 33871648 (California, 2010) 37253956 (New York, 2000) 18976457 (New York, 2010) 19378102 (Texas, 2000) 20851820 (Texas, 2010) 25145561 dtype: int64
你可以直接进行取值或者切片来查询数据。
pop[('California', 2010):('Texas', 2000)]
(California, 2010) 37253956 (New York, 2000) 18976457 (New York, 2010) 19378102 (Texas, 2000) 20851820 dtype: int64
如果你希望选择2010年数据,可能这种方法会比较繁琐:
pop[[i for i in pop.index if i[1] == 2010]]
(California, 2010) 37253956 (New York, 2010) 19378102 (Texas, 2010) 25145561 dtype: int64
这两种方法显然不够理想。我们可以考虑Pandas的多重索引(层级索引)
更好的方法: Pandas MultiIndex¶
我们可以通过创建一个多级索引来更方便对数据进行此操作。
index = pd.MultiIndex.from_tuples(index)
index
MultiIndex([('California', 2000),
('California', 2010),
( 'New York', 2000),
( 'New York', 2010),
( 'Texas', 2000),
( 'Texas', 2010)],
)
注意 MultiIndex 里面有一个levels 属性表示索引的等级,可以将州名称和时间作为每个数据点的 labels 。
如果将前面的数据重新索引,使用多种索引,这样看到的层级索引就是:
pop = pop.reindex(index)
pop
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
前两列为多级索引,第三列为数据。
直接可以使用切片方法查询需要数据,如2010年数据可以直接用下面的方法得到:
pop[: ,2010]
California 37253956 New York 19378102 Texas 25145561 dtype: int64
pop[:, 2010]
California 37253956 New York 19378102 Texas 25145561 dtype: int64
The result is a singly indexed array with just the keys we're interested in. This syntax is much more convenient (and the operation is much more efficient!) than the home-spun tuple-based multi-indexing solution that we started with. We'll now further discuss this sort of indexing operation on hieararchically indexed data.
高维数据的多级索引¶
应该可以看到,我们完全可以用简单的DataFrame代替前面的多级索引。这种方法就是unstack()方法。
pop_df = pop.unstack()
pop_df
| 2000 | 2010 | |
|---|---|---|
| California | 33871648 | 37253956 |
| New York | 18976457 | 19378102 |
| Texas | 20851820 | 25145561 |
自然, stack() 方法提供了相反的功能:
pop_df.stack()
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
我们为什么需要层级索引呢?从刚才的例子可以看出,如果我们能用多级索引的一维Series表示二维数据,那么我们就可以使用Series或者DataFrame来表示三维或者更高维度的数据。多级索引每增加一级,数据增加一个维度,利用这一特点,可以利用Pandas表示任意维度数据了。
如增加一个新的人口维度,表示18岁以下人口,应用 MultiIndex 可以方便实现:
pop_df = pd.DataFrame({'total': pop,
'under18': [9267089, 9284094,
4687374, 4318033,
5906301, 6879014]})
pop_df
| total | under18 | ||
|---|---|---|---|
| California | 2000 | 33871648 | 9267089 |
| 2010 | 37253956 | 9284094 | |
| New York | 2000 | 18976457 | 4687374 |
| 2010 | 19378102 | 4318033 | |
| Texas | 2000 | 20851820 | 5906301 |
| 2010 | 25145561 | 6879014 |
利用通用函数,可以直接得到:
f_u18 = pop_df['under18'] / pop_df['total']
f_u18.unstack()
| 2000 | 2010 | |
|---|---|---|
| California | 0.273594 | 0.249211 |
| New York | 0.247010 | 0.222831 |
| Texas | 0.283251 | 0.273568 |
这种方法可以快速浏览和操作高维度数据。
多级索引的创建方法¶
最直接方法是在Series或者DataFrame中,将Index参数设置为至少二维索引。例如:
df = pd.DataFrame(np.random.rand(4, 2),
index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
columns=['data1', 'data2'])
df
| data1 | data2 | ||
|---|---|---|---|
| a | 1 | 0.554233 | 0.356072 |
| 2 | 0.925244 | 0.219474 | |
| b | 1 | 0.441759 | 0.610054 |
| 2 | 0.171495 | 0.886688 |
MultiIndex创建工作在后台完成。
同样,如果你把元组作为键的字典传给Pandas,Pandas也会自动生成 MultiIndex。
data = {('California', 2000): 33871648,
('California', 2010): 37253956,
('Texas', 2000): 20851820,
('Texas', 2010): 25145561,
('New York', 2000): 18976457,
('New York', 2010): 19378102}
pd.Series(data)
California 2000 33871648
2010 37253956
Texas 2000 20851820
2010 25145561
New York 2000 18976457
2010 19378102
dtype: int64
尽管如此,显式创建MultiIndex也非常有用。一下是一些创建方法:
显式创建多级索引¶
pd.MultiIndex.from_arrays([['a', 'a', 'b', 'b'], [1, 2, 1, 2]])
MultiIndex([('a', 1),
('a', 2),
('b', 1),
('b', 2)],
)
也可以通过包含多个索引值的元组进行创建。
pd.MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b', 2)])
MultiIndex([('a', 1),
('a', 2),
('b', 1),
('b', 2)],
)
也可以直接使用笛卡尔乘积方法创建多级索引
pd.MultiIndex.from_product([['a', 'b'], [1, 2]])
MultiIndex([('a', 1),
('a', 2),
('b', 1),
('b', 2)],
)
相应的,也可以只用提供levels (每个等级索引值列表的列表) 和 codes (包含每个索引值标签列表的列表):
pd.MultiIndex(levels=[['a', 'b'], [1, 2]],
codes=[[0, 0, 1, 1], [0, 1, 0, 1]])
MultiIndex([('a', 1),
('a', 2),
('b', 1),
('b', 2)],
)
创建Series 或者 Dataframe对象,可以使用 reindex 方法,将上面创建的对象作为index的参数,以此创建 Series 或者 DataFrame.
多级索引的等级名称¶
有时候为MultiIndex起个名会比较方便易懂.
可以在 pd.MultiIndex构造器中,将names参数进行设置。
pop.index.names = ['state', 'year']
pop
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
在处理复杂数据时,为等级设置名称,是隔离多个索引的好办法。
多级列索引¶
在 DataFrame中,行和列是对称的,有多级行索引,也就会有多级列索引。
# 多级行列索引
index = pd.MultiIndex.from_product([[2013, 2014], [1, 2]],
names=['year', 'visit'])
columns = pd.MultiIndex.from_product([['Bob', 'Guido', 'Sue'], ['HR', 'Temp']],
names=['subject', 'type'])
# mock some data
data = np.round(np.random.randn(4, 6), 1)
data[:, ::2] *= 10
data += 37
# create the DataFrame
health_data = pd.DataFrame(data, index=index, columns=columns)
health_data
| subject | Bob | Guido | Sue | ||||
|---|---|---|---|---|---|---|---|
| type | HR | Temp | HR | Temp | HR | Temp | |
| year | visit | ||||||
| 2013 | 1 | 35.0 | 36.9 | 38.0 | 36.6 | 40.0 | 36.3 |
| 2 | 34.0 | 38.7 | 39.0 | 37.3 | 29.0 | 37.6 | |
| 2014 | 1 | 43.0 | 35.8 | 32.0 | 35.3 | 24.0 | 37.9 |
| 2 | 35.0 | 37.8 | 53.0 | 36.5 | 22.0 | 35.5 | |
多级行列索引创建非常简单,上面构建了一个四维数据。四个维度分别为被检查者姓名、检查项目、检查年份和检查项目。列索引第一级为姓名,可以获得其全部消息:
health_data['Guido']
| type | HR | Temp | |
|---|---|---|---|
| year | visit | ||
| 2013 | 1 | 38.0 | 36.6 |
| 2 | 39.0 | 37.3 | |
| 2014 | 1 | 32.0 | 35.3 |
| 2 | 53.0 | 36.5 |
多级索引的取值与切片¶
MultiIndex 取值和切片很直观,你可以将索引看成额外增加的维度。这里先介绍Series多级索引的取值和切片方法,然后是 DataFrame的相关用法。
Series的多级索引¶
假设创建多级索引Series 是刚刚介绍的州人口数据。
pop
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
可以通过对多个级别索引获取相应单个元素的值:
pop['California', 2000]
33871648
MultiIndex 同样支持局部取值,即只区索引的一个层次,如只取最高层索引,并获得一个 Series,未被选中的层次索引会被保留。
pop['California']
year 2000 33871648 2010 37253956 dtype: int64
pop.loc['California':'New York']
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
dtype: int64
pop[:, 2000]
state California 33871648 New York 18976457 Texas 20851820 dtype: int64
其他数据选择方法,使用蒙版方法选择数据实例:
pop[pop > 22000000]
state year
California 2000 33871648
2010 37253956
Texas 2010 25145561
dtype: int64
采用花式索引方法选取数据:
pop[['California', 'Texas']]
state year
California 2000 33871648
2010 37253956
Texas 2000 20851820
2010 25145561
dtype: int64
DataFrames的多级索引¶
与Series类似,DataFrame多级索引可以用之前的体检报告数据来演示:
health_data
| subject | Bob | Guido | Sue | ||||
|---|---|---|---|---|---|---|---|
| type | HR | Temp | HR | Temp | HR | Temp | |
| year | visit | ||||||
| 2013 | 1 | 35.0 | 36.9 | 38.0 | 36.6 | 40.0 | 36.3 |
| 2 | 34.0 | 38.7 | 39.0 | 37.3 | 29.0 | 37.6 | |
| 2014 | 1 | 43.0 | 35.8 | 32.0 | 35.3 | 24.0 | 37.9 |
| 2 | 35.0 | 37.8 | 53.0 | 36.5 | 22.0 | 35.5 | |
DataFrame基本索引是列索引,因此,我们看到 Guido的心率数据HR 可以直接获取:
health_data['Guido', 'HR']
year visit
2013 1 38.0
2 39.0
2014 1 32.0
2 53.0
Name: (Guido, HR), dtype: float64
与单索引类似,loc,iloc和ix索引都可以使用。
health_data.iloc[:2, :2]
| subject | Bob | ||
|---|---|---|---|
| type | HR | Temp | |
| year | visit | ||
| 2013 | 1 | 35.0 | 36.9 |
| 2 | 34.0 | 38.7 | |
这些索引器把多维数据当作二维数据来处理,但在loc或者iloc中传递多个层级的索引元组。
health_data.loc[:, ('Bob', 'HR')]
year visit
2013 1 35.0
2 34.0
2014 1 43.0
2 35.0
Name: (Bob, HR), dtype: float64
这种方法有时会出现问题。
health_data.loc[(:, 1), (:, 'HR')]
Cell In[134], line 1 health_data.loc[(:, 1), (:, 'HR')] ^ SyntaxError: invalid syntax
你可以用python内置的 slice() 函数,但是一种更好方法试试使用pandas中的IndexSlice 对象,专门解决这类问题。
idx = pd.IndexSlice
health_data.loc[idx[:, 1], idx[:, 'HR']]
| subject | Bob | Guido | Sue | |
|---|---|---|---|---|
| type | HR | HR | HR | |
| year | visit | |||
| 2013 | 1 | 35.0 | 38.0 | 40.0 |
| 2014 | 1 | 43.0 | 32.0 | 24.0 |
多级索引的Series 和 DataFrame相互交互方法有很多,需要你在应用中不断训练和熟练。
多级索引的行列转换¶
有序索引和无序索引¶
index = pd.MultiIndex.from_product([['a', 'c', 'b'], [1, 2]])
data = pd.Series(np.random.rand(6), index=index)
data.index.names = ['char', 'int']
data
char int
a 1 0.376648
2 0.119283
c 1 0.362194
2 0.438363
b 1 0.106758
2 0.410610
dtype: float64
如果进行局部切片,将会出现异常
try:
data['a':'b']
except KeyError as e:
print(type(e))
print(e)
<class 'pandas.errors.UnsortedIndexError'> 'Key length (1) was greater than MultiIndex lexsort depth (0)'
原因在于之前创建的多级索引第一层级是一个无序索引。局部切片和其他很多操作,往往要求索引是有序的(A-Z)。Pandas可以使用sort_index()等方法进行索引排序:
data = data.sort_index()
data
char int
a 1 0.376648
2 0.119283
b 1 0.106758
2 0.410610
c 1 0.362194
2 0.438363
dtype: float64
data['a':'b']
char int
a 1 0.376648
2 0.119283
b 1 0.106758
2 0.410610
dtype: float64
索引的Stack和unstack¶
之前例子中,我们可以将一个多级索引转化为简单二维形式,可以通过level参数设置转化的索引层级。
pop
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
pop.unstack(level=0)
| state | California | New York | Texas |
|---|---|---|---|
| year | |||
| 2000 | 33871648 | 18976457 | 20851820 |
| 2010 | 37253956 | 19378102 | 25145561 |
pop.unstack(level=1)
| year | 2000 | 2010 |
|---|---|---|
| state | ||
| California | 33871648 | 37253956 |
| New York | 18976457 | 19378102 |
| Texas | 20851820 | 25145561 |
unstack() 相反的行为是 stack(), 将变形后的数组转化为原始形状。
pop.unstack().stack()
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
索引的设置与重置¶
通过reset_index()方法将多级索引的Series转化为DataFrame,可以给原生数据命名。如下:
pop_flat = pop.reset_index(name='population')
pop_flat
| state | year | population | |
|---|---|---|---|
| 0 | California | 2000 | 33871648 |
| 1 | California | 2010 | 37253956 |
| 2 | New York | 2000 | 18976457 |
| 3 | New York | 2010 | 19378102 |
| 4 | Texas | 2000 | 20851820 |
| 5 | Texas | 2010 | 25145561 |
可以通过set_index()方法,直接形成多重索引
pop_flat.set_index(['state', 'year'])
| population | ||
|---|---|---|
| state | year | |
| California | 2000 | 33871648 |
| 2010 | 37253956 | |
| New York | 2000 | 18976457 |
| 2010 | 19378102 | |
| Texas | 2000 | 20851820 |
| 2010 | 25145561 |
实践中,这种重建数据索引的方法非常有用。