如何在Pandas数据框中获取某一列最大值对应的行,并保留原始索引?

3
我有一个pandas数据框架。在第一列中,可能会有相同的值出现多次(换句话说,第一列的值并不唯一)。
每当我有多行包含第一列相同的值时,我希望只保留第三列具有最大值的行。我几乎找到了一个解决方案:
import pandas

ls = []
ls.append({'c1':'a', 'c2':'a', 'c3':1})
ls.append({'c1':'a', 'c2':'c', 'c3':3})
ls.append({'c1':'a', 'c2':'b', 'c3':2})
ls.append({'c1':'b', 'c2':'b', 'c3':10})
ls.append({'c1':'b', 'c2':'c', 'c3':12})
ls.append({'c1':'b', 'c2':'a', 'c3':7})

df = pandas.DataFrame(ls, columns=['c1','c2','c3'])
print df
print '--------------------'
print df.groupby('c1').apply(lambda df:df.irow(df['c3'].argmax()))

因此,我获得了:

  c1 c2  c3
0  a  a   1
1  a  c   3
2  a  b   2
3  b  b  10
4  b  c  12
5  b  a   7
--------------------
   c1 c2  c3
c1          
a   a  c   3
b   b  c  12

我的问题是,我不想将 c1 作为索引。我想要的是以下内容:

  c1 c2  c3
1  a  c   3
4  b  c  12
2个回答

4

当调用 df.groupby(...).apply(foo) 时,foo 返回的对象类型会影响结果的合并方式。

如果返回一个Series,则该Series的索引将成为最终结果的列索引,而分组键则成为索引(有点难以理解)。

相反,如果返回一个DataFrame,则最终结果使用DataFrame的索引值作为索引,使用DataFrame的列作为列(非常合理)。

因此,您可以通过将Series转换为DataFrame来实现所需的输出类型。

从Pandas 0.13开始,您可以使用 to_frame().T 方法:

def maxrow(x, col):
    return x.loc[x[col].argmax()].to_frame().T

result = df.groupby('c1').apply(maxrow, 'c3')
result = result.reset_index(level=0, drop=True)
print(result)

产量
  c1 c2  c3
1  a  c   3
4  b  c  12

在Pandas0.12或更早版本中,相当于以下代码:
def maxrow(x, col):
    ser = x.loc[x[col].idxmax()]
    df = pd.DataFrame({ser.name: ser}).T
    return df

顺便提一下,behzad.nouri的聪明而优雅的解决方案对于较小的数据框架比我的解决方案更快。然而,sort将时间复杂度从O(n)升高到O(n log n),因此当应用于较大的数据框架时,它变得比上面展示的to_frame解决方案慢。

以下是我的基准测试方法:

import pandas as pd
import numpy as np
import timeit


def reset_df_first(df):
    df2 = df.reset_index()
    result = df2.groupby('c1').apply(lambda x: x.loc[x['c3'].idxmax()])
    result.set_index(['index'], inplace=True)
    return result

def maxrow(x, col):
    result = x.loc[x[col].argmax()].to_frame().T
    return result

def using_to_frame(df):
    result = df.groupby('c1').apply(maxrow, 'c3')
    result.reset_index(level=0, drop=True, inplace=True)
    return result

def using_sort(df):
    return df.sort('c3').groupby('c1', as_index=False).tail(1)


for N in (100, 1000, 2000):
    df = pd.DataFrame({'c1': {0: 'a', 1: 'a', 2: 'a', 3: 'b', 4: 'b', 5: 'b'},
                       'c2': {0: 'a', 1: 'c', 2: 'b', 3: 'b', 4: 'c', 5: 'a'},
                       'c3': {0: 1, 1: 3, 2: 2, 3: 10, 4: 12, 5: 7}})

    df = pd.concat([df]*N)
    df.reset_index(inplace=True, drop=True)

    timing = dict()
    for func in (reset_df_first, using_to_frame, using_sort):
        timing[func] = timeit.timeit('m.{}(m.df)'.format(func.__name__),
                              'import __main__ as m ',
                              number=10)

    print('For N = {}'.format(N))
    for func in sorted(timing, key=timing.get):
        print('{:<20}: {:<0.3g}'.format(func.__name__, timing[func]))
    print

产量
For N = 100
using_sort          : 0.018
using_to_frame      : 0.0265
reset_df_first      : 0.0303

For N = 1000
using_to_frame      : 0.0358    \
using_sort          : 0.036     / this is roughly where the two methods cross over in terms of performance
reset_df_first      : 0.0432

For N = 2000
using_to_frame      : 0.0457
reset_df_first      : 0.0523
using_sort          : 0.0569

(我尝试了另一个可能性reset_df_first。)

它将从pandas 0.13开始工作,在旧版本中,Series没有to_frame功能。 - alko
@alko:感谢您的提醒。我已添加了等效的代码,可以与0.12或更早版本一起使用。 - unutbu

1

try this:

df.sort('c3').groupby('c1', as_index=False).tail(1)

我不能强迫自己投赞成票来支持违反PEP8标准的代码;但是为了得到原帖作者期望的结果,你可能需要添加“.reset_index(level=0, drop=True)”这行代码。 - alko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接