【问题标题】:Using boolean indexing for row and column MultiIndex in Pandas在 Pandas 中对行和列 MultiIndex 使用布尔索引
【发布时间】:2014-01-12 08:00:12
【问题描述】:

问题在最后,用粗体。但首先,让我们设置一些数据:

import numpy as np
import pandas as pd
from itertools import product

np.random.seed(1)

team_names = ['Yankees', 'Mets', 'Dodgers']
jersey_numbers = [35, 71, 84]
game_numbers = [1, 2]
observer_names = ['Bill', 'John', 'Ralph']
observation_types = ['Speed', 'Strength']

row_indices = list(product(team_names, jersey_numbers, game_numbers, observer_names, observation_types))
observation_values = np.random.randn(len(row_indices))

tns, jns, gns, ons, ots = zip(*row_indices)

data = pd.DataFrame({'team': tns, 'jersey': jns, 'game': gns, 'observer': ons, 'obstype': ots, 'value': observation_values})

data = data.set_index(['team', 'jersey', 'game', 'observer', 'obstype'])
data = data.unstack(['observer', 'obstype'])
data.columns = data.columns.droplevel(0)

这给出:

我想提取此 DataFrame 的一个子集以供后续分析。假设我想切掉 jersey 数字为 71 的行。我不太喜欢使用 xs 执行此操作的想法。当您通过xs 进行横截面时,您会丢失您选择的列。如果我跑:

data.xs(71, axis=0, level='jersey')

然后我找回正确的行,但我丢失了 jersey 列。

另外,xs 对于我想要来自jersey 列的一些不同值的情况似乎不是一个很好的解决方案。我认为一个更好的解决方案是找到here

data[[j in [71, 84] for t, j, g in data.index]]

您甚至可以过滤球衣和球队的组合:

data[[j in [71, 84] and t in ['Dodgers', 'Mets'] for t, j, g in data.index]]

不错!

所以问题是:我怎样才能做类似的事情来选择列的子集。例如,假设我只想要代表来自 Ralph 的数据的列。如果不使用xs,我怎么能做到这一点?或者如果我只想要observer in ['John', 'Ralph'] 的列怎么办?同样,我真的更喜欢在结果中保留所有级别的行和列索引的解决方案......就像上面的布尔索引示例一样。

我可以做我想做的事,甚至可以组合来自行索引和列索引的选择。但我发现的唯一解决方案涉及一些真正的体操:

data[[j in [71, 84] and t in ['Dodgers', 'Mets'] for t, j, g in data.index]]\
    .T[[obs in ['John', 'Ralph'] for obs, obstype in data.columns]].T

因此是第二个问题:有没有更紧凑的方法来完成我刚才所做的事情?

【问题讨论】:

  • 很好的方法,但你的问题是什么?
  • @MattDMo 我已经加粗上面的具体问题。更一般地说:我想我在上面展示了一些强大但语法丑陋的食谱。我希望有一种更直接的方式来完成我在那里所做的事情。具体来说,我正在寻找一种方法,该方法将根据一个或多个行索引中的值限制行,同时根据一个或多个列索引中的值限制列。非常希望有人能提出更自然的方法。
  • 有趣的问题。对于单元素过滤器情况,您可以传递drop_level=False 以避免丢失Jersey 列。请注意,您可以写data.loc[[j in [71, 84] and t in ['Dodgers', 'Mets'] for t, j, g in data.index], [obs in ['John', 'Ralph'] for obs, obstype in data.columns]],而不是转置。
  • @DSM 这也是迄今为止最快的解决方案(只是),虽然 loc 在这里看起来 slow... 似乎这可能是一个很好的功能请求。
  • @AndyHayden:我不介意 df.fx(rows={"jersey": [71], "team": ["Dodgers", "Mets"]}, columns={"observer": ["John", "Ralph"]}) 这样的东西,它基本上可以满足这里的需求。

标签: python pandas multi-index


【解决方案1】:

这是一种使用更多内置感觉语法的方法。但它仍然很笨重:

data.loc[
    (data.index.get_level_values('jersey').isin([71, 84])
     & data.index.get_level_values('team').isin(['Dodgers', 'Mets'])), 
    data.columns.get_level_values('observer').isin(['John', 'Ralph'])
]

所以比较:

def hackedsyntax():
    return data[[j in [71, 84] and t in ['Dodgers', 'Mets'] for t, j, g in data.index]]\
    .T[[obs in ['John', 'Ralph'] for obs, obstype in data.columns]].T

def uglybuiltinsyntax():
    return data.loc[
        (data.index.get_level_values('jersey').isin([71, 84])
         & data.index.get_level_values('team').isin(['Dodgers', 'Mets'])), 
        data.columns.get_level_values('observer').isin(['John', 'Ralph'])
    ]

%timeit hackedsyntax()
%timeit uglybuiltinsyntax()

hackedsyntax() - uglybuiltinsyntax()

结果:

1000 loops, best of 3: 395 µs per loop
1000 loops, best of 3: 409 µs per loop

仍然希望有一种更清洁或更规范的方式来做到这一点。

【讨论】:

    【解决方案2】:

    注意:自 Pandas v0.20 起,ix 访问器已被弃用;酌情使用lociloc

    如果我正确理解了这个问题,那就很简单了:

    要获取 Ralph 的列:

    data.ix[:,"Ralph"]
    

    要为其中两个获取它,请传入一个列表:

    data.ix[:,["Ralph","John"]]
    

    ix 运算符是幂索引运算符。请记住,第一个参数是行,然后是列(与 data[..][..] 相反,相反)。冒号充当通配符,因此它返回axis=0 中的所有行。

    一般来说,要在 MultiIndex 中进行查找,您应该传入一个元组。例如

    data.[:,("Ralph","Speed")]
    

    但如果你只传入一个元素,它会将其视为传入元组的第一个元素,然后是通配符。

    如果您想访问非 0 级索引的列,就会变得棘手。例如,获取“速度”的所有列。那你就需要多一点创意了。结合使用索引/列的get_level_values 方法和布尔索引:

    例如,这会在行中获取球衣 71,在列中获取 strength

    data.ix[data.index.get_level_values("jersey") == 71 , \
            data.columns.get_level_values("obstype") == "Strength"]
    

    【讨论】:

    • 是的。我明确询问如何处理基于多索引中的非前导条目的过滤。
    • 我根据您的评论更新了答案。但请注意,您在问题中给出的示例都是针对 0 级索引的。即,您可以在列轴和行轴上都有多个索引。在您的示例中,Jack 是列多索引的第 0 级。刚刚注意到,实际上和下面的答案几乎一样。
    【解决方案3】:

    请注意,据我了解,select 很慢。但这里的另一种方法是:

    data.select(lambda col: col[0] in ['John', 'Ralph'], axis=1)

    您也可以通过对行的选择来链接它:

    data.select(lambda col: col[0] in ['John', 'Ralph'], axis=1) \
        .select(lambda row: row[1] in [71, 84] and row[2] > 1, axis=0)
    

    这里最大的缺点是您必须知道索引级别号。

    【讨论】:

      【解决方案4】:

      从 Pandas 0.18(可能更早)开始,您可以使用 pd.IndexSlice 轻松切片多索引数据帧。

      对于您的具体问题,您可以使用以下选项按球队、球衣和比赛进行选择:

      data.loc[pd.IndexSlice[:,[71, 84],:],:] #IndexSlice on the rows
      

      IndexSlice 需要足够的级别信息来明确,以便您可以删除尾随的冒号:

      data.loc[pd.IndexSlice[:,[71, 84]],:]
      

      同样,您可以在列上使用 IndexSlice:

      data.loc[pd.IndexSlice[:,[71, 84]],pd.IndexSlice[['John', 'Ralph']]]
      

      这会为您提供问题中的最终 DataFrame。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-21
        • 2018-08-18
        • 1970-01-01
        • 2018-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多