改善Pandas Merge性能

Deb*_*har 10 python merge cython pandas numba

正如其他帖子所暗示的那样,我特别没有Pands Merge的性能问题,但我有一个类,其中有很多方法,它们在数据集上进行了大量的合并.

该班有大约10个小组和大约15个合并.虽然groupby相当快,但是对于类的总执行时间为1.5秒,在这15次合并调用中大约需要0.7秒.

我想加快那些合并调用的性能.因为我将有大约4000次迭代,因此在单次迭代中总共节省0.5秒将导致整体性能降低大约30分钟,这将是很好的.

我应该尝试任何建议吗?我试过:Cython Numba,Numba比较慢.

谢谢

编辑1:添加示例代码片段:我的合并语句:

tmpDf = pd.merge(self.data, t1, on='APPT_NBR', how='left')
tmp = tmpDf

tmpDf = pd.merge(tmp, t2, on='APPT_NBR', how='left')
tmp = tmpDf

tmpDf = pd.merge(tmp, t3, on='APPT_NBR', how='left')
tmp = tmpDf

tmpDf = pd.merge(tmp, t4, on='APPT_NBR', how='left')
tmp = tmpDf

tmpDf = pd.merge(tmp, t5, on='APPT_NBR', how='left')
Run Code Online (Sandbox Code Playgroud)

并且,通过实现连接,我合并了以下声明:

dat = self.data.set_index('APPT_NBR')

t1.set_index('APPT_NBR', inplace=True)
t2.set_index('APPT_NBR', inplace=True)
t3.set_index('APPT_NBR', inplace=True)
t4.set_index('APPT_NBR', inplace=True)
t5.set_index('APPT_NBR', inplace=True)

tmpDf = dat.join(t1, how='left')
tmpDf = tmpDf.join(t2, how='left')
tmpDf = tmpDf.join(t3, how='left')
tmpDf = tmpDf.join(t4, how='left')
tmpDf = tmpDf.join(t5, how='left')

tmpDf.reset_index(inplace=True)
Run Code Online (Sandbox Code Playgroud)

注意,所有都是名为的函数的一部分:def merge_earlier_created_values(self):

并且,当我通过以下方式从profilehooks做时间调用时:

@timedcall(immediate=True)
def merge_earlier_created_values(self):
Run Code Online (Sandbox Code Playgroud)

我得到以下结果:

该方法的分析结果给出:

@profile(immediate=True)
def merge_earlier_created_values(self):
Run Code Online (Sandbox Code Playgroud)

使用Merge进行功能分析如下:

*** PROFILER RESULTS ***
merge_earlier_created_values (E:\Projects\Predictive Inbound Cartoon     Estimation-MLO\Python\CodeToSubmit\helpers\get_prev_data_by_date.py:122)
function called 1 times

     71665 function calls (70588 primitive calls) in 0.524 seconds

Ordered by: cumulative time, internal time, call count
List reduced from 563 to 40 due to restriction <40>

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.012    0.012    0.524    0.524 get_prev_data_by_date.py:122(merge_earlier_created_values)
   14    0.000    0.000    0.285    0.020 generic.py:1901(_update_inplace)
   14    0.000    0.000    0.285    0.020 generic.py:1402(_maybe_update_cacher)
   19    0.000    0.000    0.284    0.015 generic.py:1492(_check_setitem_copy)
    7    0.283    0.040    0.283    0.040 {built-in method gc.collect}
   15    0.000    0.000    0.181    0.012 generic.py:1842(drop)
   10    0.000    0.000    0.153    0.015 merge.py:26(merge)
   10    0.000    0.000    0.140    0.014 merge.py:201(get_result)
  8/4    0.000    0.000    0.126    0.031 decorators.py:65(wrapper)
    4    0.000    0.000    0.126    0.031 frame.py:3028(drop_duplicates)
    1    0.000    0.000    0.102    0.102 get_prev_data_by_date.py:264(recreate_previous_cartons)
    1    0.000    0.000    0.101    0.101 get_prev_data_by_date.py:231(recreate_previous_appt_scheduled_date)
    1    0.000    0.000    0.098    0.098 get_prev_data_by_date.py:360(recreate_previous_freight_type)
   10    0.000    0.000    0.092    0.009 internals.py:4455(concatenate_block_managers)
   10    0.001    0.000    0.088    0.009 internals.py:4471(<listcomp>)
  120    0.001    0.000    0.084    0.001 internals.py:4559(concatenate_join_units)
  266    0.004    0.000    0.067    0.000 common.py:733(take_nd)
  120    0.000    0.000    0.061    0.001 internals.py:4569(<listcomp>)
  120    0.003    0.000    0.061    0.001 internals.py:4814(get_reindexed_values)
    1    0.000    0.000    0.059    0.059 get_prev_data_by_date.py:295(recreate_previous_appt_status)
   10    0.000    0.000    0.038    0.004 merge.py:322(_get_join_info)
   10    0.001    0.000    0.036    0.004 merge.py:516(_get_join_indexers)
   25    0.001    0.000    0.024    0.001 merge.py:687(_factorize_keys)
   74    0.023    0.000    0.023    0.000 {pandas.algos.take_2d_axis1_object_object}
   50    0.022    0.000    0.022    0.000 {method 'factorize' of 'pandas.hashtable.Int64Factorizer' objects}
  120    0.003    0.000    0.022    0.000 internals.py:4479(get_empty_dtype_and_na)
   88    0.000    0.000    0.021    0.000 frame.py:1969(__getitem__)
    1    0.000    0.000    0.019    0.019 get_prev_data_by_date.py:328(recreate_previous_location_numbers)
   39    0.000    0.000    0.018    0.000 internals.py:3495(reindex_indexer)
  537    0.017    0.000    0.017    0.000 {built-in method numpy.core.multiarray.empty}
   15    0.000    0.000    0.017    0.001 ops.py:725(wrapper)
   15    0.000    0.000    0.015    0.001 frame.py:2011(_getitem_array)
   24    0.000    0.000    0.014    0.001 internals.py:3625(take)
   10    0.000    0.000    0.014    0.001 merge.py:157(__init__)
   10    0.000    0.000    0.014    0.001 merge.py:382(_get_merge_keys)
   15    0.008    0.001    0.013    0.001 ops.py:662(na_op)
  234    0.000    0.000    0.013    0.000 common.py:158(isnull)
  234    0.001    0.000    0.013    0.000 common.py:179(_isnull_new)
   15    0.000    0.000    0.012    0.001 generic.py:1609(take)
   20    0.000    0.000    0.012    0.001 generic.py:2191(reindex)
Run Code Online (Sandbox Code Playgroud)

使用Joins进行分析如下:

65079 function calls (63990 primitive calls) in 0.550 seconds

Ordered by: cumulative time, internal time, call count
List reduced from 592 to 40 due to restriction <40>

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.016    0.016    0.550    0.550 get_prev_data_by_date.py:122(merge_earlier_created_values)
   14    0.000    0.000    0.295    0.021 generic.py:1901(_update_inplace)
   14    0.000    0.000    0.295    0.021 generic.py:1402(_maybe_update_cacher)
   19    0.000    0.000    0.294    0.015 generic.py:1492(_check_setitem_copy)
    7    0.293    0.042    0.293    0.042 {built-in method gc.collect}
   10    0.000    0.000    0.173    0.017 generic.py:1842(drop)
   10    0.000    0.000    0.139    0.014 merge.py:26(merge)
  8/4    0.000    0.000    0.138    0.034 decorators.py:65(wrapper)
    4    0.000    0.000    0.138    0.034 frame.py:3028(drop_duplicates)
   10    0.000    0.000    0.132    0.013 merge.py:201(get_result)
    5    0.000    0.000    0.122    0.024 frame.py:4324(join)
    5    0.000    0.000    0.122    0.024 frame.py:4371(_join_compat)
    1    0.000    0.000    0.111    0.111 get_prev_data_by_date.py:264(recreate_previous_cartons)
    1    0.000    0.000    0.103    0.103 get_prev_data_by_date.py:231(recreate_previous_appt_scheduled_date)
    1    0.000    0.000    0.099    0.099 get_prev_data_by_date.py:360(recreate_previous_freight_type)
   10    0.000    0.000    0.093    0.009 internals.py:4455(concatenate_block_managers)
   10    0.001    0.000    0.089    0.009 internals.py:4471(<listcomp>)
  100    0.001    0.000    0.085    0.001 internals.py:4559(concatenate_join_units)
  205    0.003    0.000    0.068    0.000 common.py:733(take_nd)
  100    0.000    0.000    0.060    0.001 internals.py:4569(<listcomp>)
  100    0.001    0.000    0.060    0.001 internals.py:4814(get_reindexed_values)
    1    0.000    0.000    0.056    0.056 get_prev_data_by_date.py:295(recreate_previous_appt_status)
   10    0.000    0.000    0.033    0.003 merge.py:322(_get_join_info)
   52    0.031    0.001    0.031    0.001 {pandas.algos.take_2d_axis1_object_object}
    5    0.000    0.000    0.030    0.006 base.py:2329(join)
   37    0.001    0.000    0.027    0.001 internals.py:2754(apply)
    6    0.000    0.000    0.024    0.004 frame.py:2763(set_index)
    7    0.000    0.000    0.023    0.003 merge.py:516(_get_join_indexers)
    2    0.000    0.000    0.022    0.011 base.py:2483(_join_non_unique)
    7    0.000    0.000    0.021    0.003 generic.py:2950(copy)
    7    0.000    0.000    0.021    0.003 internals.py:3046(copy)
   84    0.000    0.000    0.020    0.000 frame.py:1969(__getitem__)
   19    0.001    0.000    0.019    0.001 merge.py:687(_factorize_keys)
  100    0.002    0.000    0.019    0.000 internals.py:4479(get_empty_dtype_and_na)
    1    0.000    0.000    0.018    0.018 get_prev_data_by_date.py:328(recreate_previous_location_numbers)
   15    0.000    0.000    0.017    0.001 ops.py:725(wrapper)
   34    0.001    0.000    0.017    0.000 internals.py:3495(reindex_indexer)
   83    0.004    0.000    0.016    0.000 internals.py:3211(_consolidate_inplace)
   68    0.015    0.000    0.015    0.000 {method 'copy' of 'numpy.ndarray' objects}
   15    0.000    0.000    0.015    0.001 frame.py:2011(_getitem_array)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,合并比连接更快,虽然它是小值,但是超过4000次迭代,这个小值变成了一个巨大的数字,只需几分钟.

谢谢

Jul*_*rec 16

我建议您将合并列设置为索引,并使用df1.join(df2)而不是merge,它会快得多。

这是一些示例,包括分析:

In [1]:
import pandas as pd
import numpy as np
df1 = pd.DataFrame(np.arange(1000000), columns=['A'])
df1['B'] = np.random.randint(0,1000,(1000000))
df2 = pd.DataFrame(np.arange(1000000), columns=['A2'])
df2['B2'] = np.random.randint(0,1000,(1000000))
Run Code Online (Sandbox Code Playgroud)

这是 A 和 A2 上的常规左合并:

In [2]: %%timeit
        x = df1.merge(df2, how='left', left_on='A', right_on='A2')

1 loop, best of 3: 441 ms per loop
Run Code Online (Sandbox Code Playgroud)

这是相同的,使用 join:

In [3]: %%timeit
        x = df1.set_index('A').join(df2.set_index('A2'), how='left')

1 loop, best of 3: 184 ms per loop
Run Code Online (Sandbox Code Playgroud)

现在很明显,如果您可以在循环之前设置索引,则时间方面的收益会大得多:

# Do this before looping
In [4]: %%time
df1.set_index('A', inplace=True)
df2.set_index('A2', inplace=True)

CPU times: user 9.78 ms, sys: 9.31 ms, total: 19.1 ms
Wall time: 16.8 ms
Run Code Online (Sandbox Code Playgroud)

然后在循环中,你会得到在这种情况下快 30 倍的东西:

In [5]: %%timeit
        x = df1.join(df2, how='left')
100 loops, best of 3: 14.3 ms per loop
Run Code Online (Sandbox Code Playgroud)

  • 不知何故,我没有看到我的数据集的性能有太大改善。如果我将所有合并转换为联接,时间会增加大约 0.1-0.3 秒。我将一些合并转换为连接,可以将时间减少约 0.2 秒。有事吗,我失踪了?或者我需要生成类似的代码吗? (2认同)
  • 很好的解决方案,但请确保保留 df 中的关键列,b/c `set_index` 默认情况下会删除它们(例如使用: `df1.set_index('A', inplace=True, drop=False) `。 (2认同)

Rei*_*ass 8

合并列上的set_index确实加快了这一点.以下是@ julien-marrec答案的更现实的版本.

import pandas as pd
import numpy as np
myids=np.random.choice(np.arange(10000000), size=1000000, replace=False)
df1 = pd.DataFrame(myids, columns=['A'])
df1['B'] = np.random.randint(0,1000,(1000000))
df2 = pd.DataFrame(np.random.permutation(myids), columns=['A2'])
df2['B2'] = np.random.randint(0,1000,(1000000))

%%timeit
    x = df1.merge(df2, how='left', left_on='A', right_on='A2')   
#1 loop, best of 3: 664 ms per loop

%%timeit  
    x = df1.set_index('A').join(df2.set_index('A2'), how='left') 
#1 loop, best of 3: 354 ms per loop

%%time 
    df1.set_index('A', inplace=True)
    df2.set_index('A2', inplace=True)
#Wall time: 16 ms

%%timeit
    x = df1.join(df2, how='left')  
#10 loops, best of 3: 80.4 ms per loop
Run Code Online (Sandbox Code Playgroud)

当要连接的列在两个表上的整数不是相同时,您仍然可以期望加速8次.

  • 一个简短的解释为什么按索引而不是按“正常”列合并更快:索引有一个哈希表。这意味着您可以在摊销的 O(1) 中查找它们。对于普通列,在最坏的情况下需要 O(n),这意味着在最坏的情况下,将两个 dfs 与 len n 合并需要 O(n^2)。 (4认同)

小智 8

我不知道这是否值得一个新的答案,但就个人而言,以下技巧帮助我进一步改进了在大型 DataFrame(数百万行和数百列)上必须执行的连接:

\n
    \n
  1. 除了使用 set_index(index, inplace=True) 之外,您可能还想使用 sort_index(inplace=True) 对其进行排序。如果您的索引未排序,这会大大加快连接速度。\n例如,使用以下命令创建 DataFrame
  2. \n
\n
import random\nimport pandas as pd\nimport numpy as np\n\nnbre_items = 100000\n\nids = np.arange(nbre_items)\nrandom.shuffle(ids)\n\ndf1 = pd.DataFrame({"id": ids})\ndf1[\'value\'] = 1\ndf1.set_index("id", inplace=True)\n\nrandom.shuffle(ids)\n\ndf2 = pd.DataFrame({"id": ids})\ndf2[\'value2\'] = 2\ndf2.set_index("id", inplace=True)\n
Run Code Online (Sandbox Code Playgroud)\n

我得到以下结果:

\n
%timeit df1.join(df2)\n13.2 ms \xc2\xb1 349 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 100 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n

对索引进行排序后(这需要有限的时间):

\n
df1.sort_index(inplace=True)\ndf2.sort_index(inplace=True)\n%timeit df1.join(df2)\n764 \xc2\xb5s \xc2\xb1 17.7 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 您可以将一个 DataFrame 拆分为多个具有较少列的 DataFrame。这个技巧给我带来了好坏参半的结果,所以使用它时要小心。\n例如:
  2. \n
\n
for i in range(0, df2.shape[1], 100):\n    df1 = df1.join(df2.iloc[:, i:min(df2.shape[1], (i + 100))], how=\'outer\')\n
Run Code Online (Sandbox Code Playgroud)\n

  • 为了兼容比较,您应该包含两个“sort_index”操作。您可以使用“%%timeit”进行多行计时,并将代码放在其下面的行中 (2认同)