我为每个组选择一个最大行,并且我使用groupby
/ agg
返回索引值并使用loc
选择行。
例如,要按"Id"
分组,然后选择"delta"
值最高的行:
selected_idx = df.groupby("Id").apply(lambda df: df.delta.argmax())
selected_rows = df.loc[selected_idx, :]
但是,这种方式很慢。实际上,当我在1300万行上使用此查询时,我的i7 / 16G RAM笔记本电脑就会挂起。
我有两个专家问题:
[更新]
非常感谢@unutbu的分析!
sort_drop
它是!在我的i7 / 32GRAM机器上,groupby + idxmax挂起了近14个小时(永远不会返回一个东西),但sort_drop
处理它的时间不过一分钟!
我仍然需要看看pandas如何实现每个方法,但现在问题已经解决了!我喜欢StackOverflow。
答案 0 :(得分:7)
最快的选项不仅取决于DataFrame的长度(在这种情况下,大约是13M行),还取决于组的数量。下面是穿孔图,比较了在每组中找到最大值的多种方法:
如果只有少数(大)组,using_idxmax
可能是最快的选项:
如果多个(小)组并且DataFrame不是太大,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
等方法对于非常大的DataFrame而言using_sort_drop
的原因。
请注意,基准测试结果可能因多种原因而有所不同,包括机器规格,操作系统和软件版本。因此,在您自己的机器上运行基准测试以及根据您的情况量身定制的测试数据非常重要。
根据上面的测试结果,using_sort_drop
可能是值得考虑的13M行的DataFrame选项,特别是如果它有很多(小)组。否则,我会怀疑using_idxmax
是最快的选择 - 但同样重要的是,检查机器上的基准测试非常重要。
以下是我用来制作perfplots的设置:
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
# https://stackoverflow.com/questions/50381064/select-the-max-row-per-group-pandas-performance-issue/50389889?noredirect=1#comment87795818_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
答案 1 :(得分:5)
from numba import njit
import numpy as np
@njit
def nidxmax(bins, k, weights):
out = np.zeros(k, np.int64)
trk = np.zeros(k)
for i, w in enumerate(weights - (weights.min() - 1)):
b = bins[i]
if w > trk[b]:
trk[b] = w
out[b] = i
return np.sort(out)
def with_numba_idxmax(df):
f, u = pd.factorize(df.Id)
return df.iloc[nidxmax(f, len(u), df.delta.values)]
从@unutbu借用
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
jit
with_numba_idxmax(make_df(10));
df = make_df(2**20)
%timeit with_numba_idxmax(df)
%timeit using_sort_drop(df)
47.4 ms ± 99.8 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
194 ms ± 451 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)