pandas DataFrame.join的运行时间(大“ O”顺序)是多少?

时间:2018-08-06 20:20:48

标签: python pandas dataframe big-o execution-time

这个问题更多是概念上的/理论上的(与非常大的数据集的运行时间有关),因此对于没有最小的示例来表示歉意。

我有一堆来自两个不同传感器的DataFrame,最终需要将它们连接成来自两个不同传感器(df_a.index = df_a.index.astype(int) df_snsr1)的两个非常大DataFrame,并且然后将其加入单个DataFrame中。我的数据使我也可以先加入,然后合并,或进行某种组合。我正在尝试找出最有效的方法。

通过阅读how to ask,我知道this SO answer为所有数据帧的级联分配了空间,如果您循环执行此操作,可能会导致df_snsr2的复制和一些重要的操作。减速。因此,我目前首先要构建一个大的数据帧列表(从文件中加载),将它们一次连接起来,然后将两个大的数据帧连接起来:

O(N**2)

我无法在pandas.concat的文档中找到有关执行速度的任何信息。是df_list = [] for file in my_pickle_files_snsr1: # O(M) loop over M files df_list.append(pd.read_pickle(file)) # O(1) append, M times df_snsr1 = pd.concat(df_list) # O(N) copies of N records # repeat for sensor 2 (df_snsr2) df_snsr1.join(df_snsr2, on=['some', 'columns']) # O(dunno, maybe bears?) 吗? O(N)?我的想法是,如果顺序类似于O(N**2),那么执行这两个操作的顺序并不重要。但是如果是pandas.concat,则效率可能更高对我来说,要加入许多小的数据框,然后将它们串联起来,而不是先进行concat然后再加入。整个操作花费了足够长的时间,值得我在这里提出问题,因此“运行并查看”将无法正常工作。

有人知道O(N**2)正在使用什么算法以及它的执行大O顺序是什么?还是有人对获得joinjoin的最有效组合有其他建议吗?

1 个答案:

答案 0 :(得分:1)

我认为这取决于您传递给join的选项(例如,联接的类型和是否排序)。

使用默认的 how='left' 时,似乎对结果进行了排序,至少是针对单个索引(文档仅指定了某些{{1 }}方法,而how不是其中之一)。无论如何,sort是inner。每个索引查找为O(n log n),其中有O(1)个。因此,在这种情况下, O(n) 占主导地位。

相比之下,在 O(n log n) 情况下,指定保持调用DataFrame的顺序。在那种情况下,我们期望 how='inner' (都可能用于设置交集以及用于索引查找和插入)。

在任何一种情况下,随着大小的增加,各种缓存局部性问题(或缺乏缓存局部性)开始在您身上蔓延,并且在随机访问中访问大内存区域所花费的实际时间将开始占主导地位。以上只是关于操作复杂性。

正如其他地方提到的,对于较大的数据集,Dask是一种行之有效的方法,即Spark。


但是您说我们如何测试它(至少在O(n)情况下)?下面的代码比我想要的要冗长一些(名称生成只是愚蠢的),但是它做到了。本质上,它使两个DF具有随机名称​​ unordered 和共同的how='left'分数;然后在测量所用时间的同时加入它们。

1 - replace_fraction

示例:尝试from IPython.core.magics.execution import _format_time as walltime def make_names(n): names = [ f'{x}{y}{z}' for (x, y), z in zip( np.random.choice(['foo', 'bar', 'hi'], (n, 2)), np.random.randint(0, n, size=n)) ] return names def work(n, replace_fraction=0.1): a_names = make_names(n) replace_n = int(n * replace_fraction) b_names = make_names(replace_n) + list(np.random.choice(a_names, size=n - replace_n, replace=False)) np.random.shuffle(b_names) a = pd.DataFrame({ 'name': a_names, 'v': np.random.uniform(size=n), 'w': np.random.uniform(size=n), }).set_index('name') b = pd.DataFrame({ 'name': b_names, 'v': np.random.uniform(size=n), 'w': np.random.uniform(size=n), }).set_index('name') t0 = time.time() df = a.join(b, rsuffix='_r') dt = time.time() - t0 return a, b, df, dt

现在,获取一系列几何尺寸的时间测量值:

work(4, .5)

适合sizes = (2**np.arange(10, 23, .5)).astype(int) times = [] for n in sizes: a, b, df, dt = work(n) times.append(dt) print(f'{n}: {walltime(dt)}') # out: 1024: 2.9 ms 1448: 4.78 ms 2048: 4.37 ms ... 2965820: 18.2 s 4194304: 30.2 s 5931641: 44.8 s

n log n

enter image description here

(旁注:from numpy.polynomial.polynomial import polyfit n = np.array(sizes) t = np.array(times) b, m = polyfit(n * np.log(n), t, 1) plt.plot(n/1e6, t, '.') plt.plot(n/1e6, b + m * n * np.log(n), '-') plt.xlabel('size [M]') plt.ylabel('time [s]') plt.show() 和所有术语scipy.optimize.nnlsnlog nn log n找出除1之外的所有系数0,所以上面很好)。