【问题标题】:How to speed up Pandas multilevel dataframe sum?如何加快 Pandas 多级数据帧总和?
【发布时间】:2013-06-30 13:35:25
【问题描述】:

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

这是一个示例:

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

更新:

我的奔腾双核 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

【问题讨论】:

  • 如需了解pd.eval() 系列函数、它们的特性和用例(以及pd.eval()df.eval() 之间的区别),请访问Dynamic Expression Evaluation in pandas using pd.eval()
  • 您的用例是什么?我想你想总结几个具有相同索引/多索引的数据帧。请注意,如果您添加具有相同索引的数据帧,则使用 numexpr 时 pandas 会更快。否则不行。使用相同的索引,我的意思是让它也以相同的方式排序。
  • 我认为您不能期望仅使用两个内核来获得很大的性能提升。

标签: python performance pandas cython


【解决方案1】:

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

正如我上面所暗示的,pandas 在后台使用numexpr 来处理某些类型的操作(在 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

【讨论】:

  • @bigbug 跳起来试试eval!它会为您对齐。目前dtype 必须至少是数字,警告那里是混合数字dtype 帧会更慢,尤其是大的,因为必须分配一个新数组来保存提升的连接数组,但它们仍然应该比直接使用 numexpr 和普通的 Python 更快。
  • @Jeff,感谢您的提示。我在我的机器上测试了你的方法(我 4 年前买的一台旧机器),数据发布在“更新”部分。开启/关闭 set_use_numexpr() 的性能差异不大,而直接 ne.evaluate('df1+df2+df3+df4') 确实提高了 2.5 倍。
  • @cpcloud。在弄清楚如何导入 eval 函数后,我将尝试 eval()。谢谢。
  • @Jeff,如果 df2, df3, df4 与 df1 不同,则 DataFrame(ne.evaluate('df1+df2+df3+df4'),columns=df1.columns,index=df1 .index,dtype='float32') 不会大幅提高速度。我想这是由于缓存。
  • @bigbug 查看您的配置,您正在运行 32 位并且有少量内存。我打赌你正在交换。
【解决方案2】:

其他观察

  • 如果您的机器上只有 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 数组上的表达式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 2013-01-19
    • 2021-03-22
    • 2019-12-16
    • 2016-10-23
    • 2020-07-16
    • 2022-06-21
    • 1970-01-01
    相关资源
    最近更新 更多