2017-07-16 286 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