При вызове df.groupby(...).apply(foo)
, тип объекта, возвращаемый foo
влияет на то, что результаты объединялись вместе.
Если вы вернете Серию, индекс Серии станет столбцом конечного результата, а ключ groupby станет индексом (немного ума-твистера).
Если вместо этого вы возвращаете DataFrame, конечный результат использует индекс DataFrame в качестве значений индекса, а столбцы DataFrame - как столбцы (очень разумные).
Таким образом, вы можете организовать желаемый тип вывода, преобразуя свою серию в DataFrame.
С Пандой 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
В Панде 0.12 или старше, эквивалент будет:
def maxrow(x, col):
ser = x.loc[x[col].idxmax()]
df = pd.DataFrame({ser.name: ser}).T
return df
Кстати, behzad.nouri's clever and elegant solution быстрее, чем мой, для небольших DataFrames. sort
повышает временную сложность от O(n)
до O(n log n)
, поэтому он становится медленнее, чем решение to_frame
, показанное выше при применении к более крупным DataFrames.
Вот как я протестированные его:
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] (https://github.com/pydata/pandas/pull/5164), в более старых версиях Series не имело функциональности 'to_frame'. – alko
@alko: Спасибо за подголовник. Я добавил эквивалентный код, который будет работать с версией 0.12 или старше. – unutbu