2016-10-14 9 views
0

Я столкнулся с огромным узким местом, где я применяю метод() к каждой строке в Pandas DataFrame. Время выполнения составляет 15-20 минут.Оптимизируйте код Python. Оптимизируйте Pandas. Numba медленный, чем чистый python

Теперь код, который я использую следующим образом:

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 

Есть ли способ, чтобы оптимизировать это применить() чтобы сократить общее время. Обратите внимание, что мне придется запустить этот процесс в DataFrame, который хранит данные в течение 2 лет. Я запускал его в течение 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) 

Compute метод 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 

Это метод обертка, который заменяет панды применить для вызова метода приписывать на каждой строке. Метод Условная оценка с помощью 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 

Я побежал метод нормальный применить(), а также Numba() метод для данных, имеющих период времени 5 дней и после были мои результаты:

With Numba: 
749805 microseconds 

With DF.apply() 
484603 microseconds. 

Как вы можете видеть, numba медленнее, чего не должно быть, поэтому на случай, если я что-то упустил, lemme знает, что я могу оптимизировать этот кусок кода.

Заранее спасибо

Редактировать 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 
+0

Вместо '~ df ['ACTL_CNTRS_BY_DAY']. Isnull()' вы можете использовать 'df ['ACTL_CNTRS_BY_DAY']. Notnull()', как незначительное улучшение. – Khris

+0

Это утверждение не является узким местом, но метод apply() для каждой строки является узким местом. –

+0

Я знаю, это была лишь незначительная вещь, которую я заметил. Можете ли вы привести пример того, как выглядят ваши данные раньше и как это должно выглядеть после? – Khris

ответ

1

Это немного помчался решение, потому что я собираюсь уйти в выходные сейчас, но это работает.

Входной Dataframe:

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 функции. Что я здесь делаю:

  • Получите все уникальные даты со значением, которое не равно null. ->tt
  • Создайте dataframe только для ненулевых значений. ->vv
  • Итерация по всем строкам и проверка, если дата в каждой строке присутствует в tt.
  • Если значение true принимает значение от vv, где дата в df одинакова и присваивает ее df.
  • Затем заполните все остальные нулевые значения 0.0.

Итерации по строкам не являются быстрым, но я надеюсь, что это быстрее, чем ваш старый код. Если бы у меня было больше времени, я бы подумал о решении без итерации, может быть, в понедельник.

EDIT: Решение без итерации с использованием 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, чтобы выбрать только существующее значение.

 Смежные вопросы

  • Нет связанных вопросов^_^