【问题标题】:Slice pandas DataFrame by MultiIndex level or sublevel按 MultiIndex 级别或子级别对 pandas DataFrame 进行切片
【发布时间】:2014-05-24 02:23:04
【问题描述】:

this answer 的启发以及this question 缺乏简单答案的启发,我发现自己编写了一些语法糖,以使生活更容易按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 开发团队(而且我开始慢慢了解了!)已经有一个很好的方法可以做到这一点,但我还不知道它是什么!

我说的对吗?

【问题讨论】:

标签: python pandas


【解决方案1】:

在 master/0.14(即将发布)中使用新的多索引切片器非常容易,请参阅here

有一个未解决的问题可以使这在语法上更容易(不难做到),请参阅here 例如这样的: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]

【讨论】:

  • 为清楚起见,在此数据帧上手动执行此操作看起来像 df.loc[pd.IndexSlice[:, :, ['C1','C3'], :], :]df.loc(axis=0)[:,:,['C1','C3'], :]
  • 有没有办法从 MultiIndex 中选择日期范围?我有df.loc[:, pd.IndexSlice[:, :, :, :, 'value']],其中第一个: 是日期,我想按日期范围进行切片,而不仅仅是一天。
  • 如果您使用 s.loc[pd.IndexSlice[:, :, ['C1','C3'], :]] 而不是 df.loc[pd.IndexSlice[:, :, ['C1','C3'], :], :],它也适用于 Series。
【解决方案2】:

我实际上赞成 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确实可以是单个值,也可以是列表、切片...

(未经面板和更高维度元素的测试,但我确实希望它可以工作)

【讨论】:

    【解决方案3】:

    你有filter 方法可以做这样的事情。例如,linked SO 问题中提出的示例:

    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
    

    过滤方法目前为refactored(即将发布的0.14),并且会添加一个level关键字(因为现在如果相同的标签出现在索引的不同级别会出现问题)。

    【讨论】:

      猜你喜欢
      • 2020-01-16
      • 1970-01-01
      • 2016-12-10
      • 2016-12-01
      • 2017-10-16
      • 2018-06-24
      • 2015-07-19
      相关资源
      最近更新 更多