在pandas多索引数据框中选择行

2024-04-19 04:54:31 发布

您现在位置:Python中文网/ 问答频道 /正文

选择/筛选adataframe whose index is a MultiIndex的行的最常见pandas方法是什么?

  • 基于单个值/标签的切片
  • 基于一个或多个级别的多个标签的切片
  • 布尔条件和表达式的过滤
  • 哪些方法适用于哪些情况

简单性假设:

  1. 输入数据帧没有重复的索引键
  2. 下面的输入数据框只有两个级别。(此处所示的大多数解推广到N个级别)

输入示例:

mux = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    list('tuvwtuvwtuvwtuvw')
], names=['one', 'two'])

df = pd.DataFrame({'col': np.arange(len(mux))}, mux)

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    u      5
    v      6
    w      7
    t      8
c   u      9
    v     10
d   w     11
    t     12
    u     13
    v     14
    w     15

问题1:选择单个项目

如何在级别“一”中选择具有“a”的行?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

另外,我如何才能在输出中降低级别“1”?

     col
two     
t      0
u      1
v      2
w      3

问题1b
如何对级别“2”上值为“t”的所有行进行切片?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

问题2:在一个级别中选择多个值

如何选择“一级”中“b”和“d”项对应的行?

         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

问题2b
我如何得到与“t”和“w”对应的所有值?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

问题3:切片单个横截面(x, y)

如何从df检索横截面,即具有索引特定值的单行?具体来说,如何检索('c', 'u')的横截面,由

         col
one two     
c   u      9

问题4:切片多个横截面[(a, b), (c, d), ...]

如何选择对应于('c', 'u')('a', 'w')的两行?

         col
one two     
c   u      9
a   w      3

问题5:每个级别一个项目切片

如何检索与级别“1”中的“a”或级别“2”中的“t”对应的所有行?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

问题6:任意切片

如何分割特定的横截面?对于“a”和“b”,我想选择所有子级别为“u”和“v”的行,对于“d”,我想选择子级别为“w”的行。

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

Question 7 will use a unique setup consisting of a numeric level:

np.random.seed(0)
mux2 = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    np.random.choice(10, size=16)
], names=['one', 'two'])

df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2)

         col
one two     
a   5      0
    0      1
    3      2
    3      3
b   7      4
    9      5
    3      6
    5      7
    2      8
c   4      9
    7     10
d   6     11
    8     12
    8     13
    1     14
    6     15

问题7:在多索引的各个级别上通过数值不等式进行过滤

如何获取级别“2”中的值大于5的所有行?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

注意:这篇文章将而不是介绍如何创建多索引,如何对它们执行赋值操作,或任何与性能相关的讨论(这些是以后单独讨论的主题)。


Tags: 数据方法fromnp切片col标签级别
2条回答

MultiIndex / Advanced Indexing

Note
This post will be structured in the following manner:

  1. The questions put forth in the OP will be addressed, one by one
  2. For each question, one or more methods applicable to solving this problem and getting the expected result will be demonstrated.

Notes (much like this one) will be included for readers interested in learning about additional functionality, implementation details, and other info cursory to the topic at hand. These notes have been compiled through scouring the docs and uncovering various obscure features, and from my own (admittedly limited) experience.

All code samples have created and tested on pandas v0.23.4, python3.7. If something is not clear, or factually incorrect, or if you did not find a solution applicable to your use case, please feel free to suggest an edit, request clarification in the comments, or open a new question, ....as applicable.

以下是一些常见的习语(以下简称“四个习语”)的介绍,我们将经常重访

  1. ^{}-按标签选择的通用解决方案(对于涉及切片的更复杂应用程序,+^{}

  2. ^{}-从序列/数据帧中提取特定的横截面。

  3. ^{}-动态指定切片和/或筛选操作(即,作为动态计算的表达式)。更适用于某些场景。有关查询多索引的信息,请参见this section of the docs

  4. 使用^{}生成的掩码的布尔索引(通常与^{}结合使用,特别是在使用多个值进行筛选时)。这在某些情况下也是非常有用的。

从这四个习语的角度来看各种切片和过滤问题,将有助于更好地理解什么可以应用于给定的情况。很重要的一点是,并非所有的习语在任何情况下都能起到同样的作用(如果有的话)。如果一个习语没有被列为解决下面问题的潜在方法,那就意味着习语不能有效地应用于这个问题。


Question 1

How do I select rows having "a" in level "one"?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

您可以使用loc,作为适用于大多数情况的通用解决方案:

df.loc[['a']]

在这一点上,如果你

TypeError: Expected tuple, got str

这意味着你用的是旧版的熊猫。考虑升级!否则,请使用df.loc[('a', slice(None)), :]

或者,您可以在这里使用xs,因为我们提取的是单个横截面。注意levelsaxis参数(这里可以假设合理的默认值)。

df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)

这里,需要drop_level=False参数来防止xs在结果中删除级别“one”(我们切片的级别)。

这里还有一个选项是使用query

df.query("one == 'a'")

如果索引没有名称,则需要将查询字符串更改为"ilevel_0 == 'a'"

最后,使用get_level_values

df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']

Additionally, how would I be able to drop level "one" in the output?

     col
two     
t      0
u      1
v      2
w      3

这可以使用

df.loc['a'] # Notice the single string argument instead the list.

或者

df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')

注意,我们可以省略drop_level参数(默认情况下假定为True)。

Note
You may notice that a filtered DataFrame may still have all the levels, even if they do not show when printing the DataFrame out. For example,

v = df.loc[['a']]
print(v)
         col
one two     
a   t      0
    u      1
    v      2
    w      3

print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])

You can get rid of these levels using MultiIndex.remove_unused_levels:

v.index = v.index.remove_unused_levels()

print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])

Question 1b

How do I slice all rows with value "t" on level "two"?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

直觉上,你会想要一些涉及^{}的东西:

df.loc[(slice(None), 't'), :]

它只是工作!™ 但它很笨重。我们可以在这里使用pd.IndexSliceAPI来简化更自然的切片语法。

idx = pd.IndexSlice
df.loc[idx[:, 't'], :]

这要干净得多。

Note
Why is the trailing slice : across the columns required? This is because, loc can be used to select and slice along both axes (axis=0 or axis=1). Without explicitly making it clear which axis the slicing is to be done on, the operation becomes ambiguous. See the big red box in the documentation on slicing.

If you want to remove any shade of ambiguity, loc accepts an axis parameter:

df.loc(axis=0)[pd.IndexSlice[:, 't']]

Without the axis parameter (i.e., just by doing df.loc[pd.IndexSlice[:, 't']]), slicing is assumed to be on the columns, and a KeyError will be raised in this circumstance.

This is documented in slicers. For the purpose of this post, however, we will explicitly specify all axes.

对于xs,它是

df.xs('t', axis=0, level=1, drop_level=False)

对于query,它是

df.query("two == 't'")
# Or, if the first level has no name, 
# df.query("ilevel_1 == 't'") 

最后,使用get_level_values,您可以

df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']

都有同样的效果。


Question 2

How can I select rows corresponding to items "b" and "d" in level "one"?

         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

使用loc,通过指定一个列表以类似的方式完成。

df.loc[['b', 'd']]

要解决上述选择“b”和“d”的问题,还可以使用query

items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')

Note
Yes, the default parser is 'pandas', but it is important to highlight this syntax isn't conventionally python. The Pandas parser generates a slightly different parse tree from the expression. This is done to make some operations more intuitive to specify. For more information, please read my post on Dynamic Expression Evaluation in pandas using pd.eval().

并且,使用get_level_values+Index.isin

df[df.index.get_level_values("one").isin(['b', 'd'])]

Question 2b

How would I get all values corresponding to "t" and "w" in level "two"?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

有了loc,这只可能与pd.IndexSlice结合使用。

df.loc[pd.IndexSlice[:, ['t', 'w']], :] 

pd.IndexSlice[:, ['t', 'w']]中,第一个冒号:表示在第一个级别上进行切片。随着所查询级别的深度增加,您将需要指定更多的切片,每个切片级别一个切片。但是,除了要切片的级别之外,您不需要指定更多级别

使用query,这是

items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas') 
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')

使用get_level_valuesIndex.isin(与上面类似):

df[df.index.get_level_values('two').isin(['t', 'w'])]

Question 3

How do I retrieve a cross section, i.e., a single row having a specific values for the index from df? Specifically, how do I retrieve the cross section of ('c', 'u'), given by

         col
one two     
c   u      9

通过指定一个键元组来使用loc

df.loc[('c', 'u'), :]

或者

df.loc[pd.IndexSlice[('c', 'u')]]

Note
At this point, you may run into a PerformanceWarning that looks like this:

PerformanceWarning: indexing past lexsort depth may impact performance.

This just means that your index is not sorted. pandas depends on the index being sorted (in this case, lexicographically, since we are dealing with string values) for optimal search and retrieval. A quick fix would be to sort your DataFrame in advance using DataFrame.sort_index. This is especially desirable from a performance standpoint if you plan on doing multiple such queries in tandem:

df_sort = df.sort_index()
df_sort.loc[('c', 'u')]

You can also use MultiIndex.is_lexsorted() to check whether the index is sorted or not. This function returns True or False accordingly. You can call this function to determine whether an additional sorting step is required or not.

对于xs,这再次简单地将一个元组作为第一个参数传递,所有其他参数都设置为相应的默认值:

df.xs(('c', 'u'))

随着query,事情变得有点笨拙:

df.query("one == 'c' and two == 'u'")

现在你可以看到这是g总的来说比较难概括。但对于这个特殊的问题还是可以的。

对于跨越多个级别的访问,get_level_values仍然可以使用,但不建议使用:

m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]

Question 4

How do I select the two rows corresponding to ('c', 'u'), and ('a', 'w')?

         col
one two     
c   u      9
a   w      3

对于loc,这仍然是简单的:

df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]

使用query,您将需要通过遍历横截面和级别动态生成查询字符串:

cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses) 

query = '(' + ') or ('.join([
    ' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)]) 
    for cs in cses
]) + ')'

print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))

df.query(query)

100%不推荐!但这是可能的。


Question 5

How can I retrieve all rows corresponding to "a" in level "one" or "t" in level "two"?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

这实际上很难在确保正确性的同时保持代码的清晰性。df.loc[pd.IndexSlice['a', 't']]不正确,它被解释为df.loc[pd.IndexSlice[('a', 't')]](即选择横截面)。您可以考虑使用pd.concat来分别处理每个标签的解决方案:

pd.concat([
    df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])

         col
one two     
a   t      0
    u      1
    v      2
    w      3
    t      0   # Does this look right to you? No, it isn't!
b   t      4
    t      8
d   t     12

但您会注意到其中一行是重复的。这是因为该行同时满足两个切片条件,因此出现了两次。你需要做的是

v = pd.concat([
        df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]

但是,如果数据帧固有地包含重复的索引(您需要的),那么这将不会保留它们。慎用

对于query,这非常简单:

df.query("one == 'a' or two == 't'")

对于get_level_values,这仍然很简单,但没有那么优雅:

m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2] 

Question 6

How can I slice specific cross sections? For "a" and "b", I would like to select all rows with sub-levels "u" and "v", and for "d", I would like to select rows with sub-level "w".

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

这是我为帮助理解四个习语的适用性而添加的一个特殊情况这是一个没有任何习语都能有效工作的情况,因为切片是非常特定的,并且不遵循任何实际模式。

通常,像这样的切片问题需要显式地将键列表传递给loc。一种方法是:

keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]

如果您想保存一些类型,您将认识到有一个模式可以对“a”、“b”及其子级别进行切片,因此我们可以将切片任务分成两部分并concat结果:

pd.concat([
     df.loc[(('a', 'b'), ('u', 'v')), :], 
     df.loc[('d', 'w'), :]
   ], axis=0)

“a”和“b”的切片规范稍微干净一些,因为索引的相同子级别对于每个级别都是相同的。


Question 7

How do I get all rows where values in level "two" are greater than 5?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

这可以使用query

df2.query("two > 5")

以及get_level_values

df2[df2.index.get_level_values('two') > 5]

Note
Similar to this example, we can filter based on any arbitrary condition using these constructs. In general, it is useful to remember that loc and xs are specifically for label-based indexing, while query and get_level_values are helpful for building general conditional masks for filtering.


Bonus Question

What if I need to slice a MultiIndexcolumn?

实际上,这里的大多数解决方案也适用于列,只需稍作更改。考虑:

np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
        list('ABCD'), list('efgh')
], names=['one','two'])

df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)

one  A           B           C           D         
two  e  f  g  h  e  f  g  h  e  f  g  h  e  f  g  h
0    5  0  3  3  7  9  3  5  2  4  7  6  8  8  1  6
1    7  7  8  1  5  9  8  9  4  3  0  3  5  0  2  3
2    8  1  3  3  3  7  0  1  9  9  0  4  7  3  2  7

以下是您需要对四个习惯用法进行的更改,以使它们能够处理列。

  1. 要使用loc切片,请使用

    df3.loc[:, ....] # Notice how we slice across the index with `:`. 
    

    或者

    df3.loc[:, pd.IndexSlice[...]]
    
  2. 要酌情使用xs,只需传递一个参数axis=1

  3. 可以使用df.columns.get_level_values直接访问列级值。然后你需要做一些

    df.loc[:, {condition}] 
    

    其中{condition}表示使用columns.get_level_values生成的某些条件。

  4. 要使用query,您唯一的选择是转置、查询索引并再次转置:

    df3.T.query(...).T
    

    不推荐,使用其他3个选项之一。

最近我遇到了一个用例,我有一个3+级别的多索引数据框架,在这个框架中,我无法使上面的任何解决方案产生我正在寻找的结果。上面的解决方案很有可能对我的用例起作用,我尝试了几个,但是在我有时间的时候,我无法让它们起作用。

我不是专家,但我偶然发现了一个没有在上面的综合答案中列出的解决方案。我不能保证这些解决方案在任何方面都是最优的。

对于上面的问题6,这是获得稍微不同结果的不同方法。(以及可能的其他问题)

特别是我要找的:

  1. 从索引的一个级别选择两个+值和从索引的另一个级别选择单个值的方法,以及
  2. 在数据帧输出中保留上一个操作的索引值的方法。

作为齿轮上的活动扳手(尽管完全固定):

  1. 索引未命名。

在下面的玩具数据框上:

    index = pd.MultiIndex.from_product([['a','b'],
                               ['stock1','stock2','stock3'],
                               ['price','volume','velocity']])

    df = pd.DataFrame([1,2,3,4,5,6,7,8,9,
                      10,11,12,13,14,15,16,17,18], 
                       index)

                        0
    a stock1 price      1
             volume     2
             velocity   3
      stock2 price      4
             volume     5
             velocity   6
      stock3 price      7
             volume     8
             velocity   9
    b stock1 price     10
             volume    11
             velocity  12
      stock2 price     13
             volume    14
             velocity  15
      stock3 price     16
             volume    17
             velocity  18

当然,使用以下作品:

    df.xs(('stock1', 'velocity'), level=(1,2))

        0
    a   3
    b  12

但我想要一个不同的结果,所以我得到这个结果的方法是:

   df.iloc[df.index.isin(['stock1'], level=1) & 
           df.index.isin(['velocity'], level=2)] 

                        0
    a stock1 velocity   3
    b stock1 velocity  12

如果我想要一个级别的两个+值和另一个级别的一个(或2+)值:

    df.iloc[df.index.isin(['stock1','stock3'], level=1) & 
            df.index.isin(['velocity'], level=2)] 

                        0
    a stock1 velocity   3
      stock3 velocity   9
    b stock1 velocity  12
      stock3 velocity  18

上面的方法可能有点笨拙,但我发现它满足了我的需要,作为奖励,我更容易理解和阅读。

相关问题 更多 >