Pandas MultiIndex groupby保留索引级别

时间:2016-06-15 20:37:07

标签: python performance pandas

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

我按其内部级别对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

3 个答案:

答案 0 :(得分:4)

在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值,而不一定返回原始行。

OLD ANSWER(使用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

这取决于当然的确切应用,但在许多情况下,这种性能差异不会很大。

答案 1 :(得分:1)

这将完成它:

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

答案 2 :(得分:1)

试试这个:: 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