优化 Python 代码。优化 Pandas 应用。 Numba 比纯 python 慢
Optimize Python code. Optimize Pandas apply. Numba slow than pure python
我面临着一个巨大的瓶颈,我将 method() 应用于 Pandas DataFrame 中的每一行。执行时间大约为 15-20 分钟。
现在,我使用的代码如下:
def FillTarget(self, df):
backup = df.copy()
target = list(set(df['ACTL_CNTRS_BY_DAY']))
df = df[~df['ACTL_CNTRS_BY_DAY'].isnull()]
tmp = df[df['ACTL_CNTRS_BY_DAY'].isin(target)]
tmp = tmp[['APPT_SCHD_ARVL_D', 'ACTL_CNTRS_BY_DAY']]
tmp.drop_duplicates(subset='APPT_SCHD_ARVL_D', inplace=True)
t1 = dt.datetime.now()
backup['ACTL_CNTRS_BY_DAY'] = backup.apply(self.ImputeTargetAcrossSameDate,args=(tmp, ), axis=1)
# backup['ACTL_CNTRS_BY_DAY'] = self.compute_(tmp, backup)
t2 = dt.datetime.now()
print("Time for the bottleneck is ", (t2-t1).microseconds)
print("step f")
return backup
并且,方法ImputeTargetAcrossSameDate()方法如下:
def ImputeTargetAcrossSameDate(self, x, tmp):
ret = tmp[tmp['APPT_SCHD_ARVL_D'] == x['APPT_SCHD_ARVL_D']]
ret = ret['ACTL_CNTRS_BY_DAY']
if ret.empty:
r = 0
else:
r = ret.values
r = r[0]
return r
有什么方法可以优化此 apply() 调用以减少总体时间。
请注意,我必须 运行 在存储数据 2 年的 DataFrame 上执行此过程。我运行宁了15天,用了15-20分钟,而运行1个月的数据时,执行了45多分钟,之后我不得不强制停止这个过程,因此当 运行 在完整数据集上运行时,这将是一个巨大的问题。
另请注意,我遇到了几个例子 http://pandas.pydata.org/pandas-docs/stable/enhancingperf.html 引入 numba 来优化代码,以下是我的 numba 实现:
调用numba方法的语句:
backup['ACTL_CNTRS_BY_DAY'] = self.compute_(tmp, backup)
numba的计算方法:
@numba.jit
def compute_(self, df1, df2):
n = len(df2)
result = np.empty(n, dtype='float64')
for i in range(n):
d = df2.iloc[i]
result[i] = self.apply_ImputeTargetAcrossSameDate_method(df1['APPT_SCHD_ARVL_D'].values, df1['ACTL_CNTRS_BY_DAY'].values,
d['APPT_SCHD_ARVL_D'], d['ACTL_CNTRS_BY_DAY'])
return result
这是替换 Pandas' 的包装器方法,适用于在每一行上调用 Impute 方法。使用numba的impute方法如下:
@numba.jit
def apply_ImputeTargetAcrossSameDate_method(self, df1col1, df1col2, df2col1, df2col2):
dd = np.datetime64(df2col1)
idx1 = np.where(df1col1 == dd)[0]
if idx1.size == 0:
idx1 = idx1
else:
idx1 = idx1[0]
val = df1col2[idx1]
if val.size == 0:
r = 0
else:
r = val
return r
我 运行 正常的 apply() 方法以及 numba() 方法用于时间段为 5 天的数据,以下是我的结果:
With Numba:
749805 microseconds
With DF.apply()
484603 microseconds.
如您所见,numba 变慢了,这不应该发生,所以如果我遗漏了什么,请告诉我,以便我可以优化这段代码。
提前致谢
编辑 1
根据要求,截取的数据(前 20 行的头部)添加如下:
之前:
APPT_SCHD_ARVL_D ACTL_CNTRS_BY_DAY
919 2020-11-17 NaN
917 2020-11-17 NaN
916 2020-11-17 NaN
915 2020-11-17 NaN
918 2020-11-17 NaN
905 2014-06-01 NaN
911 2014-06-01 NaN
913 2014-06-01 NaN
912 2014-06-01 NaN
910 2014-06-01 NaN
914 2014-06-01 NaN
908 2014-06-01 NaN
906 2014-06-01 NaN
909 2014-06-01 NaN
907 2014-06-01 NaN
898 2014-05-29 NaN
892 2014-05-29 NaN
893 2014-05-29 NaN
894 2014-05-29 NaN
895 2014-05-29 NaN
之后:
APPT_SCHD_ARVL_D ACTL_CNTRS_BY_DAY
919 2020-11-17 0.0
917 2020-11-17 0.0
916 2020-11-17 0.0
915 2020-11-17 0.0
918 2020-11-17 0.0
905 2014-06-01 0.0
911 2014-06-01 0.0
913 2014-06-01 0.0
912 2014-06-01 0.0
910 2014-06-01 0.0
914 2014-06-01 0.0
908 2014-06-01 0.0
906 2014-06-01 0.0
909 2014-06-01 0.0
907 2014-06-01 0.0
898 2014-05-29 0.0
892 2014-05-29 0.0
893 2014-05-29 0.0
894 2014-05-29 0.0
895 2014-05-29 0.0
这个方法有什么作用?
在上面的数据示例中,您可以看到一些日期是重复的,并且针对它们的值为 NaN。如果具有相同日期的所有行的值为 NaN,则将它们替换为 0。
但是在某些情况下,例如:2014-05-29 将有 10 行具有相同的日期,并且只有 1 行针对该日期会有一些值。 (比方说 10)。然后 method() 应使用 10 而不是 NaN 填充针对该特定日期的所有值。
示例:
898 2014-05-29 NaN
892 2014-05-29 NaN
893 2014-05-29 NaN
894 2014-05-29 10
895 2014-05-29 NaN
以上变为:
898 2014-05-29 10
892 2014-05-29 10
893 2014-05-29 10
894 2014-05-29 10
895 2014-05-29 10
这个解决方案有点仓促,因为我现在就要离开到周末了,但它确实有效。
输入数据帧:
index APPT_SCHD_ARVL_D ACTL_CNTRS_BY_DAY
919 2020-11-17 NaN
917 2020-11-17 NaN
916 2020-11-17 NaN
915 2020-11-17 NaN
918 2020-11-17 NaN
905 2014-06-01 NaN
911 2014-06-01 NaN
913 2014-06-01 NaN
912 2014-06-01 NaN
910 2014-06-01 NaN
914 2014-06-01 NaN
908 2014-06-01 NaN
906 2014-06-01 NaN
909 2014-06-01 NaN
907 2014-06-01 NaN
898 2014-05-29 NaN
892 2014-05-29 NaN
893 2014-05-29 NaN
894 2014-05-29 10
895 2014-05-29 NaN
898 2014-05-29 NaN
代码:
tt = df[pd.notnull(df.ACTL_CNTRS_BY_DAY)].APPT_SCHD_ARVL_D.unique()
vv = df[pd.notnull(df.ACTL_CNTRS_BY_DAY)]
for i,_ in df.iterrows():
if df.ix[i,"APPT_SCHD_ARVL_D"] in tt:
df.ix[i,"ACTL_CNTRS_BY_DAY"] = vv[vv.APPT_SCHD_ARVL_D == df.ix[i,"APPT_SCHD_ARVL_D"]]["ACTL_CNTRS_BY_DAY"].values[0]
df = df.fillna(0.0)
基本上不需要apply
一个函数。我在这里做的是:
- 获取所有具有非空值的唯一日期。 ->
tt
- 创建仅包含非空值的数据框。 ->
vv
- 遍历所有行并测试每行中的日期是否存在于
tt
。
- 如果为真,则从
vv
中取值,其中 df
中的日期相同,并将其分配给 df
。
- 然后用
0.0
填充所有其他空值。
遍历行并不是一件很快的事情,但我希望它比您的旧代码更快。如果我有更多时间,我会想到一个无需迭代的解决方案,也许在星期一。
编辑:
不使用 pd.merge()
代替迭代的解决方案:
dg = df[pd.notnull(df.ACTL_CNTRS_BY_DAY)].groupby("APPT_SCHD_ARVL_D").first()["ACTL_CNTRS_BY_DAY"].to_frame().reset_index()
df = pd.merge(df,dg,on="APPT_SCHD_ARVL_D",how='outer').rename(columns={"ACTL_CNTRS_BY_DAY_y":"ACTL_CNTRS_BY_DAY"}).drop("ACTL_CNTRS_BY_DAY_x",axis=1).fillna(0.0)
您的数据表明 ACTL_CNTRS_BY_DAY
中最多只有一个值不为空,因此我在 groupby
中使用 first()
来选择唯一存在的值.
我面临着一个巨大的瓶颈,我将 method() 应用于 Pandas DataFrame 中的每一行。执行时间大约为 15-20 分钟。
现在,我使用的代码如下:
def FillTarget(self, df):
backup = df.copy()
target = list(set(df['ACTL_CNTRS_BY_DAY']))
df = df[~df['ACTL_CNTRS_BY_DAY'].isnull()]
tmp = df[df['ACTL_CNTRS_BY_DAY'].isin(target)]
tmp = tmp[['APPT_SCHD_ARVL_D', 'ACTL_CNTRS_BY_DAY']]
tmp.drop_duplicates(subset='APPT_SCHD_ARVL_D', inplace=True)
t1 = dt.datetime.now()
backup['ACTL_CNTRS_BY_DAY'] = backup.apply(self.ImputeTargetAcrossSameDate,args=(tmp, ), axis=1)
# backup['ACTL_CNTRS_BY_DAY'] = self.compute_(tmp, backup)
t2 = dt.datetime.now()
print("Time for the bottleneck is ", (t2-t1).microseconds)
print("step f")
return backup
并且,方法ImputeTargetAcrossSameDate()方法如下:
def ImputeTargetAcrossSameDate(self, x, tmp):
ret = tmp[tmp['APPT_SCHD_ARVL_D'] == x['APPT_SCHD_ARVL_D']]
ret = ret['ACTL_CNTRS_BY_DAY']
if ret.empty:
r = 0
else:
r = ret.values
r = r[0]
return r
有什么方法可以优化此 apply() 调用以减少总体时间。 请注意,我必须 运行 在存储数据 2 年的 DataFrame 上执行此过程。我运行宁了15天,用了15-20分钟,而运行1个月的数据时,执行了45多分钟,之后我不得不强制停止这个过程,因此当 运行 在完整数据集上运行时,这将是一个巨大的问题。
另请注意,我遇到了几个例子 http://pandas.pydata.org/pandas-docs/stable/enhancingperf.html 引入 numba 来优化代码,以下是我的 numba 实现:
调用numba方法的语句:
backup['ACTL_CNTRS_BY_DAY'] = self.compute_(tmp, backup)
numba的计算方法:
@numba.jit
def compute_(self, df1, df2):
n = len(df2)
result = np.empty(n, dtype='float64')
for i in range(n):
d = df2.iloc[i]
result[i] = self.apply_ImputeTargetAcrossSameDate_method(df1['APPT_SCHD_ARVL_D'].values, df1['ACTL_CNTRS_BY_DAY'].values,
d['APPT_SCHD_ARVL_D'], d['ACTL_CNTRS_BY_DAY'])
return result
这是替换 Pandas' 的包装器方法,适用于在每一行上调用 Impute 方法。使用numba的impute方法如下:
@numba.jit
def apply_ImputeTargetAcrossSameDate_method(self, df1col1, df1col2, df2col1, df2col2):
dd = np.datetime64(df2col1)
idx1 = np.where(df1col1 == dd)[0]
if idx1.size == 0:
idx1 = idx1
else:
idx1 = idx1[0]
val = df1col2[idx1]
if val.size == 0:
r = 0
else:
r = val
return r
我 运行 正常的 apply() 方法以及 numba() 方法用于时间段为 5 天的数据,以下是我的结果:
With Numba:
749805 microseconds
With DF.apply()
484603 microseconds.
如您所见,numba 变慢了,这不应该发生,所以如果我遗漏了什么,请告诉我,以便我可以优化这段代码。
提前致谢
编辑 1 根据要求,截取的数据(前 20 行的头部)添加如下: 之前:
APPT_SCHD_ARVL_D ACTL_CNTRS_BY_DAY
919 2020-11-17 NaN
917 2020-11-17 NaN
916 2020-11-17 NaN
915 2020-11-17 NaN
918 2020-11-17 NaN
905 2014-06-01 NaN
911 2014-06-01 NaN
913 2014-06-01 NaN
912 2014-06-01 NaN
910 2014-06-01 NaN
914 2014-06-01 NaN
908 2014-06-01 NaN
906 2014-06-01 NaN
909 2014-06-01 NaN
907 2014-06-01 NaN
898 2014-05-29 NaN
892 2014-05-29 NaN
893 2014-05-29 NaN
894 2014-05-29 NaN
895 2014-05-29 NaN
之后:
APPT_SCHD_ARVL_D ACTL_CNTRS_BY_DAY
919 2020-11-17 0.0
917 2020-11-17 0.0
916 2020-11-17 0.0
915 2020-11-17 0.0
918 2020-11-17 0.0
905 2014-06-01 0.0
911 2014-06-01 0.0
913 2014-06-01 0.0
912 2014-06-01 0.0
910 2014-06-01 0.0
914 2014-06-01 0.0
908 2014-06-01 0.0
906 2014-06-01 0.0
909 2014-06-01 0.0
907 2014-06-01 0.0
898 2014-05-29 0.0
892 2014-05-29 0.0
893 2014-05-29 0.0
894 2014-05-29 0.0
895 2014-05-29 0.0
这个方法有什么作用? 在上面的数据示例中,您可以看到一些日期是重复的,并且针对它们的值为 NaN。如果具有相同日期的所有行的值为 NaN,则将它们替换为 0。 但是在某些情况下,例如:2014-05-29 将有 10 行具有相同的日期,并且只有 1 行针对该日期会有一些值。 (比方说 10)。然后 method() 应使用 10 而不是 NaN 填充针对该特定日期的所有值。
示例:
898 2014-05-29 NaN
892 2014-05-29 NaN
893 2014-05-29 NaN
894 2014-05-29 10
895 2014-05-29 NaN
以上变为:
898 2014-05-29 10
892 2014-05-29 10
893 2014-05-29 10
894 2014-05-29 10
895 2014-05-29 10
这个解决方案有点仓促,因为我现在就要离开到周末了,但它确实有效。
输入数据帧:
index APPT_SCHD_ARVL_D ACTL_CNTRS_BY_DAY
919 2020-11-17 NaN
917 2020-11-17 NaN
916 2020-11-17 NaN
915 2020-11-17 NaN
918 2020-11-17 NaN
905 2014-06-01 NaN
911 2014-06-01 NaN
913 2014-06-01 NaN
912 2014-06-01 NaN
910 2014-06-01 NaN
914 2014-06-01 NaN
908 2014-06-01 NaN
906 2014-06-01 NaN
909 2014-06-01 NaN
907 2014-06-01 NaN
898 2014-05-29 NaN
892 2014-05-29 NaN
893 2014-05-29 NaN
894 2014-05-29 10
895 2014-05-29 NaN
898 2014-05-29 NaN
代码:
tt = df[pd.notnull(df.ACTL_CNTRS_BY_DAY)].APPT_SCHD_ARVL_D.unique()
vv = df[pd.notnull(df.ACTL_CNTRS_BY_DAY)]
for i,_ in df.iterrows():
if df.ix[i,"APPT_SCHD_ARVL_D"] in tt:
df.ix[i,"ACTL_CNTRS_BY_DAY"] = vv[vv.APPT_SCHD_ARVL_D == df.ix[i,"APPT_SCHD_ARVL_D"]]["ACTL_CNTRS_BY_DAY"].values[0]
df = df.fillna(0.0)
基本上不需要apply
一个函数。我在这里做的是:
- 获取所有具有非空值的唯一日期。 ->
tt
- 创建仅包含非空值的数据框。 ->
vv
- 遍历所有行并测试每行中的日期是否存在于
tt
。 - 如果为真,则从
vv
中取值,其中df
中的日期相同,并将其分配给df
。 - 然后用
0.0
填充所有其他空值。
遍历行并不是一件很快的事情,但我希望它比您的旧代码更快。如果我有更多时间,我会想到一个无需迭代的解决方案,也许在星期一。
编辑:
不使用 pd.merge()
代替迭代的解决方案:
dg = df[pd.notnull(df.ACTL_CNTRS_BY_DAY)].groupby("APPT_SCHD_ARVL_D").first()["ACTL_CNTRS_BY_DAY"].to_frame().reset_index()
df = pd.merge(df,dg,on="APPT_SCHD_ARVL_D",how='outer').rename(columns={"ACTL_CNTRS_BY_DAY_y":"ACTL_CNTRS_BY_DAY"}).drop("ACTL_CNTRS_BY_DAY_x",axis=1).fillna(0.0)
您的数据表明 ACTL_CNTRS_BY_DAY
中最多只有一个值不为空,因此我在 groupby
中使用 first()
来选择唯一存在的值.