如何加快Pandas多级数据帧总和?

时间:2013-06-30 13:35:25

标签: python performance pandas cython

我正在努力加快几个大型多级数据帧的总和。

以下是一个示例:

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

我对993ms不满意,有没有办法加快速度? cython可以改善性能吗?如果是的话,如何编写cython代码?感谢。

注意mul_df()是创建演示多级数据帧的功能。

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

更新:

我的Pentium双核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位) )

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

2 个答案:

答案 0 :(得分:9)

方法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

方法3:直接使用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
  • 使用多核可用

正如我上面所暗示的,对于某些类型的操作(在0.11中),大熊猫使用numexpr,例如, 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

答案 1 :(得分: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

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

在这里,我以降序对DataFrame之一进行排序,因此更改了索引并重新排列了数据帧内部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,则同样如此。
  • 如果使用具有不同索引的数据帧之间的操作,则不是这种情况。
  • 如果评估包含Pandas DataFrames的表达式,甚至会导致完全错误的结果。碰巧他们可能是正确的。但是numexpression用于优化Numpy数组上的表达式。