如何切片熊猫MultiIndex df保留所有值,直到满足特定条件?

bac*_*ndr 5 python slice multi-index pandas

我有一个3级MultiIndex数据帧,我想对其进行切片,以便保留满足特定条件之前的所有值。举一个例子,我有以下数据框:

                           Col1  Col2
Date          Range  Label
'2018-08-01'  1      A     900   815
                     B     850   820
                     C     800   820
                     D     950   840
              2      A     900   820
                     B     750   850
                     C     850   820
                     D     850   800
Run Code Online (Sandbox Code Playgroud)

我想选择所有值,直到Col1变得小于Col2。一旦我有了Col1 <Col2的实例,那么我就不再关心数据了,我想删除它们(即使Col1再次大于Col2)。考虑上面的示例,这是我想要获得的数据框:

                           Col1  Col2
Date          Range  Label
'2018-08-01'  1      A     900   815
                     B     850   820
              2      A     900   820
Run Code Online (Sandbox Code Playgroud)

我尝试了几种选择,但还没有找到好的解决方案。我可以轻松保留Col1> Col2的所有数据:

df_new=df[df['Col1']>df['Col2']]
Run Code Online (Sandbox Code Playgroud)

但这不是我所需要的。我也一直在考虑循环遍历1级索引并使用pd.IndexSlice切片数据帧:

idx = pd.IndexSlice
idx_lev1=df.index.get_level_values(1).unique()

for j in (idx_lev1):
    df_lev1=df.loc[idx[:,j,:],:]
    idxs=df_lev1.index.get_level_values(2)[np.where(df_lev1['Col1']<df_lev1['Col2'])[0][0]-1]
    df_sliced= df_lev1.loc[idx[:,:,:idxs],:]
Run Code Online (Sandbox Code Playgroud)

然后将各个数据帧串联起来。但是,这效率不高(我的数据框具有超过300万个条目,因此我也必须考虑这一点),而且我遇到的问题是,对于不同的日期重复使用范围索引,因此我可能必须嵌套2个周期或相似的东西。

我敢肯定必须有一个简单且更Python化的解决方案,但我找不到解决该问题的方法。

如果要生成上面的数据框以进行测试,可以使用:

from io import StringIO
s="""                         
Date  Range  Label  Col1  Col2
'2018-08-01'  1  A  900   815
'2018-08-01'  1  B  850   820
'2018-08-01'  1  C  800   820
'2018-08-01'  1  D  950   840
'2018-08-01'  2  A  900   820
'2018-08-01'  2  B  750   850
'2018-08-01'  2  C  850   820
'2018-08-01'  2  D  850   800
"""
df2 = pd.read_csv(StringIO(s),
             sep='\s+',
             index_col=['Date','Range','Label'])
Run Code Online (Sandbox Code Playgroud)

更新:

我试图同时实施Adam.Er8Alexandre B的解决方案它们可以与我为SO创建的测试数据一起正常工作,而不能与真实数据一起工作。
问题是,在某些情况下,Col1值始终大于Col2,在这种情况下,我只想保留所有数据。到目前为止,没有提出任何解决方案可以真正解决此问题。

对于更实际的测试用例,可以使用以下示例:

s="""                         
Date  Range  Label  Col1  Col2
'2018-08-01'  1  1  900   815
'2018-08-01'  1  2  950   820
'2018-08-01'  1  3  900   820
'2018-08-01'  1  4  950   840
'2018-08-01'  2  1  900   820
'2018-08-01'  2  2  750   850
'2018-08-01'  2  3  850   820
'2018-08-01'  2  4  850   800
'2018-08-02'  1  1  900   815
'2018-08-02'  1  2  850   820
'2018-08-02'  1  3  800   820
'2018-08-02'  1  4  950   840
'2018-08-02'  2  1  900   820
'2018-08-02'  2  2  750   850
'2018-08-02'  2  3  850   820
'2018-08-02'  2  4  850   800
"""
Run Code Online (Sandbox Code Playgroud)

或者,您可以从此处下载hdf文件。这是我真正使用的数据框的子集。

Ada*_*Er8 3

我尝试对.cumcount()每一行进行编号,然后找到具有正确条件的第一行,并使用它来仅过滤编号低于该值的行。

尝试这个:

from collections import defaultdict

import pandas as pd
from io import StringIO

s="""
Date  Range  Label  Col1  Col2
'2018-08-01'  1  1  900   815
'2018-08-01'  1  2  950   820
'2018-08-01'  1  3  900   820
'2018-08-01'  1  4  950   840
'2018-08-01'  2  1  900   820
'2018-08-01'  2  2  750   850
'2018-08-01'  2  3  850   820
'2018-08-01'  2  4  850   800
'2018-08-02'  1  1  900   815
'2018-08-02'  1  2  850   820
'2018-08-02'  1  3  800   820
'2018-08-02'  1  4  950   840
'2018-08-02'  2  1  900   820
'2018-08-02'  2  2  750   850
'2018-08-02'  2  3  850   820
'2018-08-02'  2  4  850   800
"""
df = pd.read_csv(StringIO(s),
                 sep='\s+',
                 index_col=['Date', 'Range', 'Label'])

groupby_date_range = df.groupby(["Date", "Range"])
df["cumcount"] = groupby_date_range.cumcount()

first_col1_lt_col2 = defaultdict(lambda: len(df), df[df['Col1'] < df['Col2']].groupby(["Date", "Range"])["cumcount"].min().to_dict())

result = df[df.apply(lambda row: row["cumcount"] < first_col1_lt_col2[row.name[:2]], axis=1)].drop(columns="cumcount")
print(result)
Run Code Online (Sandbox Code Playgroud)

输出:

                          Col1  Col2
Date         Range Label            
'2018-08-01' 1     1       900   815
                   2       950   820
                   3       900   820
                   4       950   840
             2     1       900   820
'2018-08-02' 1     1       900   815
                   2       850   820
             2     1       900   820
Run Code Online (Sandbox Code Playgroud)