2017-07-16 291 views
1

我有一个熊猫数据框。我使用groupBy(在1列)+ apply组合向数据框添加新列。 apply通过参数调用一个自定义函数。完整的调用看起来是这样的:熊猫dataframe groupby + apply +新列慢

df = df.groupby('id').apply(lambda x: customFunction(x,'searchString')) 

自定义功能的工作原理如下:基于一个ifelse情况下,新列要么充满了10。然后该团队返回。广义一点,自定义函数如下:

def customFunction(group,searchString): 
    #print(group.iloc[[0]]['id'].values[0]) 
    if len(group[(group['name'] == searchString)) > 0: 
     group['newColumn'] = 1 
    else: 
     group['newColumn'] = 0 
    return group 

我的问题是,脚本运行比较长,即使我不真的多的数据处理。这些是我的数据的统计数据: 数据帧有3130行和49列。 groupBy生成1499个独立的组。

如果我在customFunction中输出了一些调试文本,我观察到通过每个组的实际迭代非常快,但在最后它需要几秒(比迭代本身更长),直到groupBy实际完成。我认为这与重新索引或重新分配新列中的新数据有关。

我的问题,现在:

  • 为什么groupBy + apply需要这么长时间?为什么实际迭代已经完成的部分需要很长时间?
  • 如何避免这个瓶颈?我如何改进我的代码(见上文)以更快地执行?
  • 更一般地说:如何将模式“按特定列分组然后添加基于条件的新列”可以最有效地实现?也许有一种方法是创建一个单独的数据结构,而不需要返回组。然后,在一个单独的步骤中,新计算的数据结构可以与原始数据框结合。但是,我不太确定这是否会更好。

我已阅读,应避免回组,因为它需要很长,但我觉得在我的情况下,它是必要的,因为我明确我customFunction生成新的数据,而这需要返回数据。

+1

请将样本数据 –

+0

尝试在应用函数之前对其进行聚合:'df.groupby('id')。sum()。apply(...)' –

+0

需要很长的时间,因为每行都会调用您的自定义函数。你想做什么?应该可以使用更快的技术。 –

回答

2

下面是另一种更有效的(对于该特定情况下)溶液而不groupby

>> searchString = 'searchString' 
>> df = pd.DataFrame({'id': np.random.choice(1000, 1000000)}) 
>> df['name'] = random_names # 1000000 random strings of len 10 
>> df.loc[np.random.choice(1000000, 1000, replace=False), 'name'] = searchString 
>> 
>> def solution_0(x): 
>> x = x.groupby('id').apply(lambda g: customFunction(g, searchString)) 
>> 
>> def solution_1(x): 
>> x['newColumn'] = x.groupby('id')['name'].transform(lambda g: g.eq(searchString).any().astype(int)) 
>> 
>> def solution_2(x): 
>> x['newColumn'] = 0 
>> x.loc[x['id'].isin(x.loc[x['name'] == searchString, 'id']), 'newColumn'] = 1 
>> 
>> %timeit solution_0(df) 
3.4 s ± 125 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 
>> %timeit solution_1(df) 
1.47 s ± 56.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 
>> %timeit solution_2(df) 
129 ms ± 4.33 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 
+0

哇。不太可读,但是(使用我的数据)“solution_2”比“solution_1”快3倍。 – beta

+0

这很聪明! – MaxU

2

df.groupby(...).apply(...)没有完全向量化,因为它是一个for .. loop,它将为每个组应用指定的函数(在您的情况下它将被执行1499次+1次)。

See Notes in the docs describing why Pandas apply will call func twice for the first group

在目前的实现应用调用FUNC第一 组两次以决定是否可以采取快或慢的代码路径。如果func有副作用,这可能导致意想不到的行为,因为它们 将对第一组生效两次。

建议首先查找使用矢量化函数的解决方案,如果不可能使用.apply()作为最后的手段。

IIUC可以使用下面的矢量的方法:

In [43]: df 
Out[43]: 
    id name 
0 1 aaa 
1 1 bbb 
2 1 aaa 
3 2 ccc 
4 2 bbb 
5 2 ccc 
6 3 aaa 

In [44]: searchString = 'aaa' 

In [45]: df['newColumn'] = df.groupby('id')['name'] \ 
          .transform(lambda x: x.eq(searchString).any().astype(int)) 

In [46]: df 
Out[46]: 
    id name newColumn 
0 1 aaa   1 
1 1 bbb   1 
2 1 aaa   1 
3 2 ccc   0 
4 2 bbb   0 
5 2 ccc   0 
6 3 aaa   1 

时序为70.000行DF:

In [56]: df = pd.concat([df] * 10**4, ignore_index=True) 

In [57]: df.shape 
Out[57]: (70000, 2) 

In [58]: %timeit df.groupby('id').apply(lambda x: customFunction(x,searchString)) 
10 loops, best of 3: 92.4 ms per loop 

In [59]: %timeit df.groupby('id')['name'].transform(lambda x: x.eq(searchString).any().astype(int)) 
10 loops, best of 3: 53.5 ms per loop 
+0

感谢这个解决方案。只是另一个相关的问题:'变换'不是'引擎盖下的'...循环'吗? – beta

+1

@beta,这很难说。一个将不得不检查源代码...我已经添加了时间 – MaxU

+0

谢谢。我现在很急。我会稍后详细阅读您的答案,然后接受为答案(然后删除此评论)。谢谢! – beta