Mar*_*end 6 python performance cython pandas numba
我正在尝试根据信息对及其以前的值在数据框中创建一个新列。虽然我运行的代码是正确的,并给出了我需要的结果,但当我在大型数据帧上运行它时速度非常慢。所以我怀疑我没有使用所有的 Python 功能来完成这项任务。在 Python 中是否有更有效、更快的方法来做到这一点?
为了让您了解上下文,让我向您解释一下我在寻找什么:
我有一个数据框,它描述了比赛结果,对于每个“日期”,您可以看到参加比赛的“类型”及其名为'xx' 的分数。
我的代码所做的是获取每个“日期”的“类型”之间的分数“xx”的差异,然后获取过去所有类型相互竞争的先前比赛结果的差异总和( 'win_comp_past_difs')。
您可以在下面看到数据和模型及其输出。
## I. DATA AND MODEL ##
Run Code Online (Sandbox Code Playgroud)
I.1. 数据
import pandas as pd
import numpy as np
idx = [np.array(['Jan-18', 'Jan-18', 'Feb-18', 'Mar-18', 'Mar-18', 'Mar-18','Mar-18', 'Mar-18', 'May-18', 'Jun-18', 'Jun-18', 'Jun-18','Jul-18', 'Aug-18', 'Aug-18', 'Sep-18', 'Sep-18', 'Oct-18','Oct-18', 'Oct-18', 'Nov-18', 'Dec-18', 'Dec-18',]),np.array(['A', 'B', 'B', 'A', 'B', 'C', 'D', 'E', 'B', 'A', 'B', 'C','A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'A', 'B', 'C'])]
data = [{'xx': 1}, {'xx': 5}, {'xx': 3}, {'xx': 2}, {'xx': 7}, {'xx': 3},{'xx': 1}, {'xx': 6}, {'xx': 3}, {'xx': 5}, {'xx': 2}, {'xx': 3},{'xx': 1}, {'xx': 9}, {'xx': 3}, {'xx': 2}, {'xx': 7}, {'xx': 3}, {'xx': 6}, {'xx': 8}, {'xx': 2}, {'xx': 7}, {'xx': 9}]
df = pd.DataFrame(data, index=idx, columns=['xx'])
df.index.names=['date','type']
df=df.reset_index()
df['date'] = pd.to_datetime(df['date'],format = '%b-%y')
df=df.set_index(['date','type'])
df['xx'] = df.xx.astype('float')
Run Code Online (Sandbox Code Playgroud)
看起来像这样:
xx
date type
2018-01-01 A 1.0
B 5.0
2018-02-01 B 3.0
2018-03-01 A 2.0
B 7.0
C 3.0
D 1.0
E 6.0
2018-05-01 B 3.0
2018-06-01 A 5.0
B 2.0
C 3.0
2018-07-01 A 1.0
2018-08-01 B 9.0
C 3.0
2018-09-01 A 2.0
B 7.0
2018-10-01 C 3.0
A 6.0
B 8.0
2018-11-01 A 2.0
2018-12-01 B 7.0
C 9.0
Run Code Online (Sandbox Code Playgroud)
I.2. 模型(在大型数据帧中非常慢)
xx
date type
2018-01-01 A 1.0
B 5.0
2018-02-01 B 3.0
2018-03-01 A 2.0
B 7.0
C 3.0
D 1.0
E 6.0
2018-05-01 B 3.0
2018-06-01 A 5.0
B 2.0
C 3.0
2018-07-01 A 1.0
2018-08-01 B 9.0
C 3.0
2018-09-01 A 2.0
B 7.0
2018-10-01 C 3.0
A 6.0
B 8.0
2018-11-01 A 2.0
2018-12-01 B 7.0
C 9.0
Run Code Online (Sandbox Code Playgroud)
您可以在下面看到模型的输出如何:
# get differences of pairs, useful for win counts and win_difs
def get_diff(x):
teams = x.index.get_level_values(1)
tmp = pd.DataFrame(x[:,None]-x[None,:],columns = teams.values,index=teams.values).stack()
return tmp[tmp.index.get_level_values(0)!=tmp.index.get_level_values(1)]
new_df = df.groupby('date').xx.apply(get_diff).to_frame()
# group by players
groups = new_df.groupby(level=[1,2])
# sum function
def cumsum_shift(x):
return x.cumsum().shift()
# assign new values
df['win_comp_past_difs'] = groups.xx.apply(cumsum_shift).sum(level=[0,1])
Run Code Online (Sandbox Code Playgroud)
以防万一您难以理解用户定义函数 (def) 的作用,让我在下面向您解释。
对于这个海豚,我将使用数据帧的一组 groupby。
下面您将看到用户定义功能如何工作的说明。
xx win_comp_past_difs
date type
2018-01-01 A 1.0 0.0
B 5.0 0.0
2018-02-01 B 3.0 NaN
2018-03-01 A 2.0 -4.0
B 7.0 4.0
C 3.0 0.0
D 1.0 0.0
E 6.0 0.0
2018-05-01 B 3.0 NaN
2018-06-01 A 5.0 -10.0
B 2.0 13.0
C 3.0 -3.0
2018-07-01 A 1.0 NaN
2018-08-01 B 9.0 3.0
C 3.0 -3.0
2018-09-01 A 2.0 -6.0
B 7.0 6.0
2018-10-01 C 3.0 -10.0
A 6.0 -10.0
B 8.0 20.0
2018-11-01 A 2.0 NaN
2018-12-01 B 7.0 14.0
C 9.0 -14.0
Run Code Online (Sandbox Code Playgroud)
因此,为了让您了解用户定义函数的工作原理,让我选择 groupby 的特定组。
II.1 选择特定组
## II. EXPLANATION OF THE USER-DEFINED FUNCTION ##
Run Code Online (Sandbox Code Playgroud)
看起来像这样:
xx
date type
2018-03-01 A 2.0
B 7.0
C 3.0
D 1.0
E 6.0
Run Code Online (Sandbox Code Playgroud)
II.2 创建参赛者(团队)名单'
gb = df.groupby('date')
gb2 = gb.get_group((list(gb.groups)[2]))
Run Code Online (Sandbox Code Playgroud)
II.3 创建'type'之间'xx'差异的数据帧
xx
date type
2018-03-01 A 2.0
B 7.0
C 3.0
D 1.0
E 6.0
Run Code Online (Sandbox Code Playgroud)
看起来像这样:
A B C D E
A 0.0 -5.0 -1.0 1.0 -4.0
B 5.0 0.0 4.0 6.0 1.0
C 1.0 -4.0 0.0 2.0 -3.0
D -1.0 -6.0 -2.0 0.0 -5.0
E 4.0 -1.0 3.0 5.0 0.0
Run Code Online (Sandbox Code Playgroud)
从这一点开始,我使用 stack() 函数作为返回原始数据帧的中间步骤。其余的您可以在 I. DATA AND MODEL 中遵循它。
如果您能详细说明代码以使其更高效且执行速度更快,我将不胜感激。
我只修改了get_diff. 要点是移到stack外部get_diff,利用stack其下降的特点NaN,避免内部过滤get_diff。
新get_diff_s功能np.fill用于填充所有对角线值NaN并返回数据帧,而不是过滤后的系列。
def get_diff_s(x):\n teams = x.index.get_level_values(1)\n arr = x[:,None]-x[None,:]\n np.fill_diagonal(arr, np.nan) \n return pd.DataFrame(arr,columns = teams.values,index=teams.values)\n\ndf['win_comp_past_difs'] = (df.groupby('date').xx.apply(get_diff_s)\n .groupby(level=1).cumsum().stack()\n .groupby(level=[1,2]).shift().sum(level=[0, 1]))\n\nOut[1348]:\n xx win_comp_past_difs\ndate type\n2018-01-01 A 1.0 0.0\n B 5.0 0.0\n2018-02-01 B 3.0 NaN\n2018-03-01 A 2.0 -4.0\n B 7.0 4.0\n C 3.0 0.0\n D 1.0 0.0\n E 6.0 0.0\n2018-05-01 B 3.0 NaN\n2018-06-01 A 5.0 -10.0\n B 2.0 13.0\n C 3.0 -3.0\n2018-07-01 A 1.0 NaN\n2018-08-01 B 9.0 3.0\n C 3.0 -3.0\n2018-09-01 A 2.0 -6.0\n B 7.0 6.0\n2018-10-01 C 3.0 -10.0\n A 6.0 -10.0\n B 8.0 20.0\n2018-11-01 A 2.0 NaN\n2018-12-01 B 7.0 14.0\n C 9.0 -14.0\nRun Code Online (Sandbox Code Playgroud)\n\n时间安排:
\n\n原始解决方案:(我将所有命令链接到一行中)
\n\nIn [1352]: %timeit df.groupby('date').xx.apply(get_diff).groupby(level=[1,2]).a\n ...: pply(lambda x: x.cumsum().shift()).sum(level=[0,1])\n82.9 ms \xc2\xb1 2.12 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\nRun Code Online (Sandbox Code Playgroud)\n\n修改后的解决方案:
\n\nIn [1353]: %timeit df.groupby('date').xx.apply(get_diff_s).groupby(level=1).cum\n ...: sum().stack().groupby(level=[1,2]).shift().sum(level=[0,1])\n47.1 ms \xc2\xb1 1.51 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\nRun Code Online (Sandbox Code Playgroud)\n\n因此,在样本数据上,速度大约快了 40%。但是,我不知道它在您的真实数据集上的表现如何
\n