按MultiIndex级别或子级别切片pandas DataFrame
受到这个回答的启发,以及对这个问题没有简单答案的困扰,我写了一些小代码,让在MultiIndex层级中筛选变得更简单。
def _filter_series(x, level_name, filter_by):
"""
Filter a pd.Series or pd.DataFrame x by `filter_by` on the MultiIndex level
`level_name`
Uses `pd.Index.get_level_values()` in the background. `filter_by` is either
a string or an iterable.
"""
if isinstance(x, pd.Series) or isinstance(x, pd.DataFrame):
if type(filter_by) is str:
filter_by = [filter_by]
index = x.index.get_level_values(level_name).isin(filter_by)
return x[index]
else:
print "Not a pandas object"
不过,如果我对pandas开发团队了解得越来越多(我正在慢慢了解!),我觉得他们应该已经有一个很好的方法来做到这一点,只是我还不知道是什么!
我说得对吗?
3 个回答
1
你可以使用filter
这个方法来做一些事情。例如,在这个链接中的问题里提到的例子:
In [188]: df.filter(like='0630', axis=0)
Out[188]:
sales cogs net_pft
STK_ID RPT_Date
876 20060630 857483000 729541000 67157200
20070630 1146245000 1050808000 113468500
20080630 1932470000 1777010000 133756300
2254 20070630 501221000 289167000 118012200
目前,filter
方法正在进行重构(在即将发布的0.14版本中),会增加一个level
参数(因为现在如果同样的标签出现在索引的不同层级时,可能会出现问题)。
5
使用新版本的多重索引切片功能,这个操作非常简单,版本是master/0.14(很快就会发布),你可以在这里查看相关信息。
目前有一个开放的问题,目的是让这个语法更简单(其实并不难),你可以在这里找到。比如说,像这样写:df.loc[{ 'third' : ['C1','C3'] }]
我觉得是合理的。
下面是如何操作的(需要使用master/0.14版本):
In [2]: def mklbl(prefix,n):
...: return ["%s%s" % (prefix,i) for i in range(n)]
...:
In [11]: index = MultiIndex.from_product([mklbl('A',4),
mklbl('B',2),
mklbl('C',4),
mklbl('D',2)],names=['first','second','third','fourth'])
In [12]: columns = ['value']
In [13]: df = DataFrame(np.arange(len(index)*len(columns)).reshape((len(index),len(columns))),index=index,columns=columns).sortlevel()
In [14]: df
Out[14]:
value
first second third fourth
A0 B0 C0 D0 0
D1 1
C1 D0 2
D1 3
C2 D0 4
D1 5
C3 D0 6
D1 7
B1 C0 D0 8
D1 9
C1 D0 10
D1 11
C2 D0 12
D1 13
C3 D0 14
D1 15
A1 B0 C0 D0 16
D1 17
C1 D0 18
D1 19
C2 D0 20
D1 21
C3 D0 22
D1 23
B1 C0 D0 24
D1 25
C1 D0 26
D1 27
C2 D0 28
D1 29
C3 D0 30
D1 31
A2 B0 C0 D0 32
D1 33
C1 D0 34
D1 35
C2 D0 36
D1 37
C3 D0 38
D1 39
B1 C0 D0 40
D1 41
C1 D0 42
D1 43
C2 D0 44
D1 45
C3 D0 46
D1 47
A3 B0 C0 D0 48
D1 49
C1 D0 50
D1 51
C2 D0 52
D1 53
C3 D0 54
D1 55
B1 C0 D0 56
D1 57
C1 D0 58
D1 59
...
[64 rows x 1 columns]
在所有层级中创建一个索引器,选择所有条目。
In [15]: indexer = [slice(None)]*len(df.index.names)
让我们关心的层级只包含我们需要的条目。
In [16]: indexer[df.index.names.index('third')] = ['C1','C3']
选择它(这里要注意,这必须是一个元组!)
In [18]: df.loc[tuple(indexer),:]
Out[18]:
value
first second third fourth
A0 B0 C1 D0 2
D1 3
C3 D0 6
D1 7
B1 C1 D0 10
D1 11
C3 D0 14
D1 15
A1 B0 C1 D0 18
D1 19
C3 D0 22
D1 23
B1 C1 D0 26
D1 27
C3 D0 30
D1 31
A2 B0 C1 D0 34
D1 35
C3 D0 38
D1 39
B1 C1 D0 42
D1 43
C3 D0 46
D1 47
A3 B0 C1 D0 50
D1 51
C3 D0 54
D1 55
B1 C1 D0 58
D1 59
C3 D0 62
D1 63
[32 rows x 1 columns]
6
我其实给joris的回答点了赞……但不幸的是,他提到的重构在0.14版本中并没有发生,在0.17版本中也没有。所以目前我想给你推荐一个简单粗暴的解决方案(显然是从Jeff的方案中得来的):
def filter_by(df, constraints):
"""Filter MultiIndex by sublevels."""
indexer = [constraints[name] if name in constraints else slice(None)
for name in df.index.names]
return df.loc[tuple(indexer)] if len(df.shape) == 1 else df.loc[tuple(indexer),]
pd.Series.filter_by = filter_by
pd.DataFrame.filter_by = filter_by
……可以这样使用
df.filter_by({'level_name' : value})
这里的value
实际上可以是一个单一的值,也可以是一个列表,或者一个切片……
(在面板和更高维度的元素上没有经过测试,但我预计它应该能工作)