Самый быстрый вариант зависит не только от длины DataFrame (в данном случае около 13M строк), но и от количества групп. Ниже приведены перфлоплоты, которые сравнивают несколько способов нахождения максимума в каждой группе:
Если существует только несколько (больших) групп , using_idxmax
может быть самым быстрым вариантом:
Если имеется много (маленьких) групп и размер Фрейма данных не слишком велик , using_sort_drop
может быть самым быстрым вариантом:
Имейте в виду, однако, что хотя using_sort_drop
, using_sort
и using_rank
начинают выглядеть очень быстро, по мере увеличения N = len(df)
их скорость относительно других параметров быстро исчезает. Для достаточно больших N
, using_idxmax
становится самым быстрым вариантом , даже если есть много групп.
using_sort_drop
, using_sort
и using_rank
сортирует DataFrame (или группы в DataFrame). В среднем сортировка составляет O(N * log(N))
, тогда как другие методы используют операции O(N)
. Вот почему такие методы, как using_idxmax
, бьют using_sort_drop
для очень больших фреймов данных.
Имейте в виду, что результаты тестов могут отличаться по ряду причин, включая характеристики компьютера, ОС и версии программного обеспечения. Поэтому важно запускать тесты на своем компьютере и с тестовыми данными, адаптированными к вашей ситуации.
Исходя из вышеприведенных перфлотов, using_sort_drop
может быть - вариант, который стоит рассмотреть для вашего DataFrame из 13M строк, особенно если в нем много (маленьких) групп. В противном случае, я подозреваю, что using_idxmax
будет самым быстрым вариантом, но, опять же, важно проверить эталонные тесты на вашем компьютере.
Вот настройка, которую я использовал для создания перфлоплот :
import numpy as np
import pandas as pd
import perfplot
def make_df(N):
# lots of small groups
df = pd.DataFrame(np.random.randint(N//10+1, size=(N, 2)), columns=['Id','delta'])
# few large groups
# df = pd.DataFrame(np.random.randint(10, size=(N, 2)), columns=['Id','delta'])
return df
def using_idxmax(df):
return df.loc[df.groupby("Id")['delta'].idxmax()]
def max_mask(s):
i = np.asarray(s).argmax()
result = [False]*len(s)
result[i] = True
return result
def using_custom_mask(df):
mask = df.groupby("Id")['delta'].transform(max_mask)
return df.loc[mask]
def using_isin(df):
idx = df.groupby("Id")['delta'].idxmax()
mask = df.index.isin(idx)
return df.loc[mask]
def using_sort(df):
df = df.sort_values(by=['delta'], ascending=False, kind='mergesort')
return df.groupby('Id', as_index=False).first()
def using_rank(df):
mask = (df.groupby('Id')['delta'].rank(method='first', ascending=False) == 1)
return df.loc[mask]
def using_sort_drop(df):
# Thanks to jezrael
# /11039184/vyberite-maksimalnoe-kolichestvo-strok-v-gruppe-problema-s-proizvoditelnosty-pandcomment87795818_50389889
return df.sort_values(by=['delta'], ascending=False, kind='mergesort').drop_duplicates('Id')
def using_apply(df):
selected_idx = df.groupby("Id").apply(lambda df: df.delta.argmax())
return df.loc[selected_idx]
def check(df1, df2):
df1 = df1.sort_values(by=['Id','delta'], kind='mergesort').reset_index(drop=True)
df2 = df2.sort_values(by=['Id','delta'], kind='mergesort').reset_index(drop=True)
return df1.equals(df2)
perfplot.show(
setup=make_df,
kernels=[using_idxmax, using_custom_mask, using_isin, using_sort,
using_rank, using_apply, using_sort_drop],
n_range=[2**k for k in range(2, 20)],
logx=True,
logy=True,
xlabel='len(df)',
repeat=75,
equality_check=check)
Другой способ оценки производительности - использовать IPython% timeit :
In [55]: df = make_df(2**20)
In [56]: %timeit using_sort_drop(df)
1 loop, best of 3: 403 ms per loop
In [57]: %timeit using_rank(df)
1 loop, best of 3: 1.04 s per loop
In [58]: %timeit using_idxmax(df)
1 loop, best of 3: 15.8 s per loop