1

I am trying to speed up the sum for several big multilevel dataframes.

Here is a sample:

df1 = mul_df(5000,30,400) # mul_df to create a big multilevel dataframe
#let df2, df3, df4 = df1, df1, df1 to minimize the memory usage, 
#they can also be mul_df(5000,30,400) 
df2, df3, df4 = df1, df1, df1

In [12]: timeit df1+df2+df3+df4
1 loops, best of 3: 993 ms per loop

I am not satisfy with the 993ms, Is there any way to speed up ? Can cython improve the performance ? If yes, how to write the cython code ? Thanks.

Note: mul_df() is the function to create the demo multilevel dataframe.

import itertools
import numpy as np
import pandas as pd

def mul_df(level1_rownum, level2_rownum, col_num, data_ty='float32'):
    ''' create multilevel dataframe, for example: mul_df(4,2,6)'''

    index_name = ['STK_ID','RPT_Date']
    col_name = ['COL'+str(x).zfill(3) for x in range(col_num)]

    first_level_dt = [['A'+str(x).zfill(4)]*level2_rownum for x in range(level1_rownum)]
    first_level_dt = list(itertools.chain(*first_level_dt)) #flatten the list
    second_level_dt = ['B'+str(x).zfill(3) for x in range(level2_rownum)]*level1_rownum

    dt = pd.DataFrame(np.random.randn(level1_rownum*level2_rownum, col_num), columns=col_name, dtype = data_ty)
    dt[index_name[0]] = first_level_dt
    dt[index_name[1]] = second_level_dt

    rst = dt.set_index(index_name, drop=True, inplace=False)
    return rst

Update:

Data on my Pentium Dual-Core T4200@2.00GHZ, 3.00GB RAM, WindowXP, Python 2.7.4, Numpy 1.7.1, Pandas 0.11.0, numexpr 2.0.1 (Anaconda 1.5.0 (32-bit))

In [1]: from pandas.core import expressions as expr
In [2]: import numexpr as ne

In [3]: df1 = mul_df(5000,30,400)
In [4]: df2, df3, df4 = df1, df1, df1

In [5]: expr.set_use_numexpr(False)
In [6]: %timeit df1+df2+df3+df4
1 loops, best of 3: 1.06 s per loop

In [7]: expr.set_use_numexpr(True)
In [8]: %timeit df1+df2+df3+df4
1 loops, best of 3: 986 ms per loop

In [9]: %timeit  DataFrame(ne.evaluate('df1+df2+df3+df4'),columns=df1.columns,index=df1.index,dtype='float32')
1 loops, best of 3: 388 ms per loop
4

2 回答 2

8

方法1:在我的机器上还不错(numexpr禁用)

In [41]: from pandas.core import expressions as expr

In [42]: expr.set_use_numexpr(False)

In [43]: %timeit df1+df2+df3+df4
1 loops, best of 3: 349 ms per loop

方法2:使用numexpr(如果numexpr安装了默认启用)

In [44]: expr.set_use_numexpr(True)

In [45]: %timeit df1+df2+df3+df4
10 loops, best of 3: 173 ms per loop

方法三:直接使用numexpr

In [34]: import numexpr as ne

In [46]: %timeit  DataFrame(ne.evaluate('df1+df2+df3+df4'),columns=df1.columns,index=df1.index,dtype='float32')
10 loops, best of 3: 47.7 ms per loop

这些加速是通过以下方式实现的numexpr

  • 避免使用中间临时数组(在你展示的情况下,在 numpy 中可能效率很低,我怀疑这被评估为((df1+df2)+df3)+df4
  • 尽可能使用多核

正如我上面所暗示的,pandasnumexpr在幕后使用某些类型的操作(在 0.11 中),例如df1 + df2会以这种方式进行评估,但是您在此处给出的示例将导致多次调用numexpr(这是方法 2 比方法 1 快.)。使用直接(方法 3)ne.evaluate(...)可以实现更多的加速。

请注意,在 pandas 0.13(本周将发布 0.12)中,我们实现了一个功能pd.eval,该功能实际上与我上面的示例完全相同。请继续关注(如果您喜欢冒险,这将很快成为大师:https ://github.com/pydata/pandas/pull/4037 )

In [5]: %timeit pd.eval('df1+df2+df3+df4')
10 loops, best of 3: 50.9 ms per loop

最后回答你的问题,cython在这里根本无济于事;numexpr在这类问题上非常有效(也就是说,在某些情况下 cython 很有帮助)

一个警告:为了使用直接 Numexpr 方法,帧应该已经对齐(Numexpr 在 numpy 数组上运行并且对索引一无所知)。他们也应该是一个单一的dtype

于 2013-06-30T19:28:48.997 回答
0

其他观察

  • 如果您的机器上只有 2 个内核,您就不能期望更快的速度。最后 numexpression 依赖于并行化和 cpu 缓存的高性能使用。
  • 你所做的在某种程度上是错误的。DataFrames 上的 Numexpressions 很快,但是是错误的。如果 DataFrame 的索引不同,它们不会返回正确的结果。不同的排序已经给你带来了麻烦,我将在下面展示。
  • 如果您添加具有不同索引的 DataFrame,那么整个东西的性能就不再那么好了。好吧,Pandas 通过查找相应的索引条目来为您添加正确的行做得很好。这伴随着自然成本。

在以下我的观察中: - 首先,我复制您的测试用例并得出其他结果。在 Pandas 的底层使用 numexpression 可以显着提高性能。- 其次,我按降序对四个 DataFrame 之一进行排序并重新运行所有案例。性能中断,此外,(如预期的那样)对 Pandas DataFrames 的 numexpression 评估会导致错误的结果。

所有帧上的相等索引

这个案例再现了你的案例。唯一的区别是,我创建了初始 DataFrame 实例的副本。所以没有什么共享的。使用不同的对象(id)来确保 numexpression 可以处理它。

import itertools
import numpy as np
import pandas as pd

def mul_df(level1_rownum, level2_rownum, col_num, data_ty='float32'):
    ''' create multilevel dataframe, for example: mul_df(4,2,6)'''

    index_name = ['STK_ID','RPT_Date']
    col_name = ['COL'+str(x).zfill(3) for x in range(col_num)]

    first_level_dt = [['A'+str(x).zfill(4)]*level2_rownum for x in range(level1_rownum)]
    first_level_dt = list(itertools.chain(*first_level_dt)) #flatten the list
    second_level_dt = ['B'+str(x).zfill(3) for x in range(level2_rownum)]*level1_rownum

    dt = pd.DataFrame(np.random.randn(level1_rownum*level2_rownum, col_num), columns=col_name, dtype = data_ty)
    dt[index_name[0]] = first_level_dt
    dt[index_name[1]] = second_level_dt

    rst = dt.set_index(index_name, drop=True, inplace=False)
    return rst
df1 = mul_df(5000,30,400)
df2, df3, df4 = df1.copy(), df1.copy(), df1.copy() 
pd.options.compute.use_numexpr = False
%%timeit
df1 + df2 + df3 + df4
564 ms ± 10.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
pd.options.compute.use_numexpr = True
%%timeit 
df1 + df2 + df3 + df4
152 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
import numexpr as ne
%%timeit
pd.DataFrame(ne.evaluate('df1 + df2 + df3 + df4'), columns=df1.columns, index=df1.index, dtype='float32')
66.4 ms ± 1.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
(df1 + df2 + df3 + df4).equals(pd.DataFrame(ne.evaluate('df1 + df2 + df3 + df4'), columns=df1.columns, index=df1.index, dtype='float32'))
True

(略微)某些帧上的不同索引

在这里,我按降序对数据帧之一进行排序,因此更改索引并重新洗牌数据帧内部 numpy 数组中的行。

import itertools
import numpy as np
import pandas as pd

def mul_df(level1_rownum, level2_rownum, col_num, data_ty='float32'):
    ''' create multilevel dataframe, for example: mul_df(4,2,6)'''

    index_name = ['STK_ID','RPT_Date']
    col_name = ['COL'+str(x).zfill(3) for x in range(col_num)]

    first_level_dt = [['A'+str(x).zfill(4)]*level2_rownum for x in range(level1_rownum)]
    first_level_dt = list(itertools.chain(*first_level_dt)) #flatten the list
    second_level_dt = ['B'+str(x).zfill(3) for x in range(level2_rownum)]*level1_rownum

    dt = pd.DataFrame(np.random.randn(level1_rownum*level2_rownum, col_num), columns=col_name, dtype = data_ty)
    dt[index_name[0]] = first_level_dt
    dt[index_name[1]] = second_level_dt

    rst = dt.set_index(index_name, drop=True, inplace=False)
    return rst
df1 = mul_df(5000,30,400)
df2, df3, df4 = df1.copy(), df1.copy(), df1.copy().sort_index(ascending=False)
pd.options.compute.use_numexpr = False
%%timeit
df1 + df2 + df3 + df4
1.36 s ± 67.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
pd.options.compute.use_numexpr = True
%%timeit 
df1 + df2 + df3 + df4
928 ms ± 39.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
import numexpr as ne
%%timeit
pd.DataFrame(ne.evaluate('df1 + df2 + df3 + df4'), columns=df1.columns, index=df1.index, dtype='float32')
68 ms ± 2.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
(df1 + df2 + df3 + df4).equals(pd.DataFrame(ne.evaluate('df1 + df2 + df3 + df4'), columns=df1.columns, index=df1.index, dtype='float32'))
False

结论

通过使用numexpr

  • 在同等索引的 DataFrame 上运行时,获得了相当大的加速。
  • 如果您有具有单个数据框的其他表达式,则同样如此,例如2 * df1.
  • 如果使用具有不同索引的 DataFrame 之间的操作,则情况并非如此。
  • 如果评估包含 Pandas DataFrames 的表达式,甚至会导致完全错误的结果。碰巧他们可能是对的。但是 numexpression 用于优化 Numpy 数组上的表达式。
于 2020-06-17T12:15:43.207 回答