Как я могу заменить цикл FOR чем-то более быстрым - PullRequest
0 голосов
/ 02 ноября 2019

Я пытаюсь преобразовать мой фрейм данных на основе определенных условий. Ниже приводится мой входной фрейм данных

In [11]: df
Out[11]: 
   DocumentNumber     I_Date     N_Date     P_Date  Amount
0            1234 2016-01-01 2017-01-01 2017-10-23   38.38
1            2345 2016-01-02 2017-01-02 2018-03-26   41.00
2            1324 2016-01-12 2017-01-03 2018-03-26   30.37
3            5421 2016-01-13 2017-01-02 2018-03-06  269.00
4            5532 2016-01-15 2017-01-04 2018-06-30  271.00

Желаемое решение:

  • Каждая строка является уникальным документом, и моя цель состоит в том, чтобы найти количество документов и их общее количество, которые соответствуютупомянутое условие, если я работаю для каждого дня и комбинации дельта.
  • Я могу достичь желаемого результата с помощью for-loop, но я знаю, что это не идеальный способ и он замедляется по мере увеличения моих данных. Поскольку я новичок в Python, мне нужна помощь, чтобы избавиться от цикла с помощью понимания списка или любой другой более быстрой опции.

Код:

d1 = datetime.date(2017, 1, 1)
d2 = datetime.date(2017, 1, 15)

mydates = pd.date_range(d1, d2).tolist()
Delta = pd.Series(range(0,5)).tolist()

df_A =[]


for i in mydates: 
    for j in Delta:
        A = df[(df["I_Date"]<i) & (df["N_Date"]>i+j) & (df["P_Date"]>i) ]
        A["DateCutoff"] = i
        A["Delta"]=j
        A = A.groupby(['DateCutoff','Delta'],as_index=False).agg({'Amount':'sum','DocumentNumber':'count'})
        A.columns = ['DateCutoff','Delta','A_PaymentAmount','A_DocumentNumber']
        df_A.append(A)

df_A = pd.concat(df_A, sort = False)

Выход:

In [14]: df_A
Out[14]: 
  DateCutoff  Delta  A_PaymentAmount  A_DocumentNumber
0 2017-01-01      0           611.37                 4
0 2017-01-01      1           301.37                 2
0 2017-01-01      2           271.00                 1
0 2017-01-02      0           301.37                 2
0 2017-01-02      1           271.00                 1
0 2017-01-03      0           271.00                 1

1 Ответ

2 голосов
/ 02 ноября 2019
  1. Я не вижу способа удалить цикл из вашего кода, потому что цикл создает отдельные кадры данных на основе содержимого mydates и Delta.
    • В этом примере вы создаете 75 различных фреймов данных
    • На каждом фрейме данных вы .groupby, затем .agg sum платежей и count номеров документов.
    • Каждый фрейм данных добавляется в список.
    • pd.concat полный список в фрейме данных.
  2. Одно значительное улучшение
    • Проверьте логическое условие перед созданием кадра данных и выполнением оставшихся операций. В этом примере операции были выполнены на 69 пустых фреймах данных. Сначала проверяя условие, операции будут выполняться только на 6 фреймах данных, содержащих данные.
    • condition.any() возвращает True, если хотя бы один элемент равен True
  3. Незначительные изменения
    • datetime + int устарело, поэтому измените его на datetime + timedelta(days=x)
    • pd.Series(range(0,5)).tolist() излишне для составления списка. Теперь необходимо timedelta объектов, поэтому используйте [timedelta(days=x) for x in range(5)]
    • Вместо итерации с двумя for-loops, используйте itertools.product для mydates и Delta. Это создает генератор кортежей в форме (Timestamp('2017-01-01 00:00:00', freq='D'), datetime.timedelta(0))
    • Используйте .copy() при создании фрейма данных A, чтобы предотвратить SettingWithCopyWarning
  4. Примечание:
    • A list comprehension был упомянут в вопросе. Это просто pythonic способ сделать for-loop, но не обязательно улучшать производительность.
    • Все вычисления используют методы панд, а не for-loops. for-loop создает кадр данных только из условия.

Обновленный код:

from itertools import product
import pandas as pd
from datetime import date, timedelta

d1 = date(2017, 1, 1)
d2 = date(2017, 1, 15)

mydates = pd.date_range(d1, d2)
Delta = [timedelta(days=x) for x in range(5)]

df_list = list()

for t in product(mydates, Delta):
    condition = (df["I_Date"]<t[0]) & (df["N_Date"]>t[0]+t[1]) & (df["P_Date"]>t[0])
    if condition.any():
        A = df[condition].copy()
        A["DateCutoff"] = t[0]
        A["Delta"] = t[1]
        A = A.groupby(['DateCutoff','Delta'],as_index=False).agg({'Amount':'sum','DocumentNumber':'count'})
        A.columns = ['DateCutoff','Delta','A_PaymentAmount','A_DocumentNumber']
        df_list.append(A)

df_CutOff = pd.concat(df_list, sort = False)

Выходные данные

  • такой же, как оригинал
  DateCutoff  Delta  A_PaymentAmount  A_DocumentNumber
0 2017-01-01      0           611.37                 4
0 2017-01-01      1           301.37                 2
0 2017-01-01      2           271.00                 1
0 2017-01-02      0           301.37                 2
0 2017-01-02      1           271.00                 1
0 2017-01-03      0           271.00                 1
...