【发布时间】:2019-06-03 02:17:43
【问题描述】:
我有一个数据框
ID CAT SCORE
0 0 0 8325804
1 0 1 1484405
... ... ... ...
1999980 99999 0 4614037
1999981 99999 1 1818470
我将数据按ID 分组,并想知道每个 ID 中得分最高的 2 个类别。我可以看到两种解决方案:
df2 = df.groupby('ID').apply(lambda g: g.nlargest(2, columns='SCORE'))
或手动将其转换为元组列表,对元组进行排序,删除除两个之外的每个 ID,然后转换回数据帧。第一个应该比第二个快得多,但我观察到手动解决方案要快得多。
为什么手动 nlargest 比 pandas 解决方案更快?
MVCE
import numpy as np
import pandas as pd
import time
def create_df(n=10**5, categories=20):
np.random.seed(0)
df = pd.DataFrame({'ID': [id_ for id_ in range(n) for c in range(categories)],
'CAT': [c for id_ in range(n) for c in range(categories)],
'SCORE': np.random.randint(10**7, size=n * categories)})
return df
def are_dfs_equal(df1, df2):
columns = sorted(df1.columns)
if len(df1.columns) != len(df2.columns):
return False
elif not all(el1 == el2 for el1, el2 in zip(columns, sorted(df2.columns))):
return False
df1_list = [tuple(x) for x in df1[columns].values]
df1_list = sorted(df1_list, reverse=True)
df2_list = [tuple(x) for x in df2[columns].values]
df2_list = sorted(df2_list, reverse=True)
is_same = df1_list == df2_list
return is_same
def manual_nlargest(df, n=2):
df_list = [tuple(x) for x in df[['ID', 'SCORE', 'CAT']].values]
df_list = sorted(df_list, reverse=True)
l = []
current_id = None
current_id_count = 0
for el in df_list:
if el[0] != current_id:
current_id = el[0]
current_id_count = 1
else:
current_id_count += 1
if current_id_count <= n:
l.append(el)
df = pd.DataFrame(l, columns=['ID', 'SCORE', 'CAT'])
return df
df = create_df()
t0 = time.time()
df2 = df.groupby('ID').apply(lambda g: g.nlargest(2, columns='SCORE'))
t1 = time.time()
print('nlargest solution: {:0.2f}s'.format(t1 - t0))
t0 = time.time()
df3 = manual_nlargest(df, n=2)
t1 = time.time()
print('manual nlargest solution: {:0.2f}s'.format(t1 - t0))
print('is_same: {}'.format(are_dfs_equal(df2, df3)))
给予
nlargest solution: 97.76s
manual nlargest solution: 4.62s
is_same: True
【问题讨论】:
-
这比你的 apply() 快,但比手动慢:df_group = df.groupby(['ID'])['SCORE'].nlargest(2)
-
对于此类基准测试,如果您想要相当可靠的结果,您应该使用
timeit模块。话虽如此,使用 timeit 测试您的代码,我仍然会在两种解决方案之间得到一致且巨大的差异(=~63 vs =~ 5s)。只是一个问题:您是否检查过两种解决方案的结果是否相同? -
@brunodesthuilliers 我敢肯定它是一样的。请查看更新。
-
@SandervandenOord 是的,它更快。但后来我只得到了 ID / 分数,但失去了
CAT。
标签: python pandas dataframe pandas-groupby