【问题标题】:Pandas MultiIndex groupby retaining index levelsPandas MultiIndex groupby 保留索引级别
【发布时间】:2016-06-15 20:37:06
【问题描述】:

经过研究,我在这个论坛或任何其他论坛上都没有发现类似的问题。

我正在按内部级别对 MultiIndex 数据帧进行分组。问题是,在分组之后,我仍然想知道这个内部索引上哪些是“选择的值”。

所以我有类似的东西

df = pd.DataFrame([['A', 1, 3],
                   ['A', 2, 4],
                   ['A', 3, 6],
                   ['B', 1, 9],
                   ['B', 2, 10],
                   ['B', 4, 6]],
                  columns=pd.Index(['Name', 'Date', 'Value'], name='ColumnName')
                 ).set_index(['Name', 'Date'])

ColumnName         Value
Name    Date
A        1           3
         2           4
         3           6 
B        1           9
         2           10
         4           6

我想要的是

ColumnName         Value
Name    Date
A        3           6
B        4           6

我能做的就是使用这个命令:

df.groupby(level=('Name')).last()

正在检索这个:

ColumnName         Value
Name    
A                    6
B                    6

或者,使用以下命令:

df.groupby(level=('Name','Date')).last()

检索错误。

请记住,这是一个性能敏感的应用程序。

想法?

编辑:同时我确实提交了feature request at GitHub

【问题讨论】:

    标签: python performance pandas


    【解决方案1】:

    通过在 groupby 对象上使用 tail(1) 而不是 last(),您可以获得所需的行为:

    In [22]: df.groupby(level='Name').tail(1)
    Out[22]:
    ColumnName  Value
    Name Date
    A    3          6
    B    4          6
    

    这是因为tail 的作用类似于“过滤器”方法,保持原始索引不变(但仅返回某些行,在本例中为每个组的最后一行)。 last 不这样做,因为此方法将为您提供每个组中每列的最后一个非 NaN 值,不一定返回原始行。


    旧答案(使用last):您可以使用groupby 将要保留在groupby 中的索引级别作为一列简单地实现:

    In [44]: df.reset_index(level='Date').groupby(level=0).last()
    Out[44]:
    ColumnName  Date  Value
    Name
    A              3      6
    B              4      6
    

    然后您可以将其设置回索引以获得所需的结果:

    In [46]: df.reset_index(level='Date').groupby(level=0).last().set_index('Date', append=True)
    Out[46]:
    ColumnName  Value
    Name Date
    A    3          6
    B    4          6
    

    由于被问及性能,确实 groupby 解决方案在示例数据帧上较慢:

    In [96]: %timeit get_slice(df)
    1000 loops, best of 3: 879 µs per loop
    
    In [97]: %timeit df.reset_index(level='Date').groupby(level='Name').last().set_index('Date', append=True)
    100 loops, best of 3: 3.75 ms per loop
    
    In [220]: %timeit df.groupby(level='Name').tail(1)
    1000 loops, best of 3: 1.04 ms per loop
    

    但是如果你看一个更大的示例数据框,差异已经小得多(last 方法更快):

    In [83]: df1 = pd.DataFrame(
                 {'Value':np.random.randint(100, size=len(string.letters)*100)}, 
                 index=pd.MultiIndex.from_product([list(string.letters), range(100)],
                                                  names=['Name', 'Date']))
    
    In [84]: df1
    Out[84]:
               Value
    Name Date
    a    0        13
         1         9
         2        11
         3        16
    ...          ...
    Z    96       15
         97       20
         98       40
         99       91
    
    [5200 rows x 1 columns]
    
    In [85]: %timeit get_slice(df1)
    100 loops, best of 3: 3.24 ms per loop
    
    In [86]: %timeit df1.reset_index(level='Date').groupby(level='Name').last().set_index('Date', append=True)
    100 loops, best of 3: 4.69 ms per loop
    
    In [218]: %timeit df1.groupby(level='Name').tail(1)
    1000 loops, best of 3: 1.66 ms per loop
    

    这当然取决于具体的应用,但在许多情况下,这种性能差异并不显着。

    【讨论】:

    • 如果您跟踪对我和 op 帖子的编辑,您会发现 op 对性能很感兴趣。我们经历了许多与此类似的解决方案。使用 timeit 进行比较。
    • 我添加了一些比较。确实更快(虽然我怀疑如果组数变大,差异会更小)。
    • @piRSquared 我找到了一个更简单的解决方案,而且速度也更快!
    • 这很好。问题是,我已经对这个答案投了赞成票。不能做两次;-)
    【解决方案2】:

    这样就搞定了:

    def get_slice(df):
        l0, l1 = df.index.levels
        b0, b1 = df.index.labels
    
        n = len(l0)
        myslice = range(n)
    
        for i in myslice:
            myslice[i] = (l0[i], l1[b1[b0 == i][-1]])
    
        return df.loc[myslice]
    

    定时

    %%timeit
    get_slice(df)
    
    1000 loops, best of 3: 458 µs per loop
    

    【讨论】:

    • 我看到你编辑了你的帖子。通常,从一开始就将其作为考虑因素。我帮你查。
    • 同意您的修改。这不是本机行为仍然很奇怪。我会寻找功能请求部分或其他内容
    • 我用一个更好的解决方案编辑了这篇文章。虽然不太直观。
    • 谢谢,这行得通。我仍然认为应该有一个本地选项来这样做。我来看看代码。虽然我不希望自己能做到。
    • 顺便说一句,如果索引中有重复值(在 OPs 应用程序中当然不一定是这种情况),此解决方案将不会给出正确答案,尽管这可能可以通过使用 iloc 来解决地点。
    【解决方案3】:

    试试这个::reset_index()

    df = pd.DataFrame([['A', 1, 3],
                       ['A', 2, 4],
                       ['A', 3, 6],
                       ['B', 1, 9],
                       ['B', 2, 10],
                       ['B', 4, 6]],
                      columns=pd.Index(['Name', 'Date', 'Value'], name='ColumnName')
                     ).set_index(['Name', 'Date'])
    
    df = df.reset_index()
    df2 = df.groupby(["Name"])["Name","Date","Value" ].last()
    df2.set_index(['Name', 'Date'], inplace=True)
    
    #            Value
    # Name Date       
    # A    3         6
    # B    4         6
    

    【讨论】:

    • 如果您跟踪对我和 op 帖子的编辑,您会发现 op 对性能很感兴趣。我们经历了许多与此类似的解决方案。使用 timeit 进行比较。
    • 对不起,我的意思是操作正在寻找效率。您很可能会找到更好的解决方案。甚至可能看起来像我已经尝试过的东西。但是,您应该考虑将解决方案计时作为解决方案的一部分。否则,您的答案如何叠加并不明显。如果这是一个更好的解决方案,我希望操作员会选择你的而不是我的。我上次的评论不清楚。我急着去开会。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-19
    • 2023-03-10
    • 2015-07-19
    • 2019-11-19
    • 2018-12-20
    • 2018-02-03
    • 2019-10-18
    相关资源
    最近更新 更多