优化Python代码。优化熊猫适用。 Numba比纯python慢​​

时间:2016-10-14 10:34:57

标签: python pandas optimization jit numba

我面临着一个巨大的瓶颈,我将方法()应用于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()调用以减少总时间。 请注意,我必须在DataFrame上运行此过程,DataFrame存储数据2年。我运行了15天,它花了我15-20分钟,而当运行1个月的数据时,它执行超过45分钟,之后我不得不强制停止进程,因此在完整数据集上运行,这将是一个巨大的问题。

另请注意,我在介绍numba优化代码时遇到了一些示例http://pandas.pydata.org/pandas-docs/stable/enhancingperf.html,以下是我的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的估算方法如下:

@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

对于时间为5天的数据,我运行了普通的apply()方法和numba()方法,以下是我的结果:

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)。然后方法()应使用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

1 个答案:

答案 0 :(得分:1)

这是一个有点匆忙的解决方案,因为我现在即将离开周末,但它确实有效。

输入数据框:

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中每行的日期是否存在。
  • 如果为true,则取vvdf中的日期相同的值,并将其分配给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中最多只有一个值不为空,因此我在first()中使用groupby来选择唯一存在的值。< / p>