如何加快Pandas多级数据框的求和速度?

问题描述 投票:1回答:2

我想加快几个大的多级数据帧的和。

下面是一个例子。

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

更新了一下,我的奔腾双核,3.00GB内存,WindowXP,Python 2.7.4,Numpy 1.7.1。

我的奔腾双核[email protected],3.00GB内存,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
python performance pandas cython
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

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

正如我上面所提示的,大熊猫使用的是 numexpr 在0.11中,对于某些类型的操作,比如说 df1 + df2 将以这种方式进行评估,但是您在这里举的例子会导致多次调用 numexpr 这是方法2比方法1快)。使用直接(方法3)。ne.evaluate(...) 实现了更多的加速。

请注意,在pandas 0.13(0.12将在本周发布),我们实现了一个函数 pd.eval 这实际上就是我上面的例子所做的事情。请继续关注(如果你是冒险家,这将是在主人有点很快。https:/github.compydatapandaspull4037)。)

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

最后回答你的问题。cython 在这里根本帮不上忙。numexpr 是相当有效的这种类型的问题(这就是说,有 益处)

有一点需要注意:为了使用直接的Numexpr方法,框架应该已经对齐了(Numexpr在numpy数组上操作,不知道任何关于索引的信息)。


0
投票

其他意见

  • 如果您的机器上只有2个核心,您就不能期望更多的速度。最后,numexpression依赖于并行化和cpu缓存的使用。
  • 你的做法在某种程度上是错误的。DataFrames上的Numexpressions是快速的,但却是错误的。如果DataFrames的索引不一样,它们就不会返回正确的结果。不同的排序已经会给你带来麻烦,我在下面展示。
  • 如果你添加了不同索引的DataFrames,整个东西的性能就不是那么好了。好吧,Pandas做得相当不错,通过查找corrrsponding索引项,为你添加合适的行。这自然是要付出代价的。

以下是我的观察:-首先,我重现了你的测试案例,并得出了其他结果。在Pandas的罩子下使用numexpression可以显著提高性能.-其次,我将四个DataFrames中的一个按降序排序,并重新运行所有案例。在Pandas DataFrames上评估numexpression会导致错误的结果。

所有帧上的指数相等

这个案例重现了你的案例,唯一不同的是,我创建了最初的DataFrame实例的副本。唯一不同的是,我创建了初始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

在一些框架上的(略微)不同的索引。

在这里,我对其中一个DataFrames进行了降序排序,因此改变了索引,并对数据框架内部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

  • 在同等索引的DataFrames上操作时,可以获得不少的速度提升。
  • 如果你有其他的表达式与一个数据帧,也是如此,如 2 * df1.
  • 如果使用不同指数的DataFrames之间的操作,情况就不是这样了。
  • 如果评估包含Pandas DataFrame的表达式,甚至会导致完全错误的结果。偶然的情况下,它们可能是正确的。但是numexpression是为优化Numpy数组上的表达式而生的。
© www.soinside.com 2019 - 2024. All rights reserved.