Ускорьте взгляд вперед, за поиском временных рядов в пандах DataFrame - PullRequest
0 голосов
/ 27 сентября 2019

У меня есть пандас DataFrame, включающий элементы с сайта электронной коммерции.Я проверяю, существуют ли элементы во фрейме данных в последующие (забегая вперед) или в предыдущие (позади) дни, с помощью функции, подобной этой:

#get a unique set of product urls for the next 2 days. If the current row's URL is in that set, mark it as 0 (not expiring); otherwise mark it 1 (expiring).

def is_expiring_product(row):
    if row.childurl in df[(df['date'] > row['date']) & (df['date'] < (row['date']  + np.timedelta64(2,"D")) ) ].childurl.unique():
        row.is_expiring_product = 0
    else:
        row.is_expiring_product = 1
    return row

Проблема в том, что для этого требуются возрасты(24 часа +, если он когда-либо завершится) для применения к кадру из 1M + записей.

Интуиция говорит мне, что для этого должен быть более производительный метод ...

Возможно, создание отдельного DataFrameуникальных URL-адресов для каждого дня, а затем поиск по этому DataFrame вместо большего кадра?Я не уверен, почему это было бы быстрее, хотя ...

1 Ответ

0 голосов
/ 28 сентября 2019

Пример кода # 1: Вычислить is_expiring_product для одной даты

Для теста я использовал следующий DataFrame ( date столбец типа datetime64 [нс] ):

        date  url
0 2019-09-10  Xxx
1 2019-09-12  Xxx
2 2019-09-14  Xxx
3 2019-09-11  Yyy
4 2019-09-12  Yyy
5 2019-09-12  Zz1
6 2019-09-12  Zz2
7 2019-09-08  Ttt
8 2019-09-12  Ttt
9 2019-09-15  Ttt

Отправной точкой является определение 2 переменных, используемых при нарезке df , на date , в ячейки( Past , Today и Future ):

dMin = pd.to_datetime(0)            # "Minimal" date
dMax = pd.to_datetime('2050-12-31') # "Maximal" date

Предположим, что мы хотим вычислить is_expiring_product только для 2019-09-12 , поэтому в первую очередь необходимо установить currDate (также будет использоваться при резке):

currDate = pd.to_datetime('2019-09-12')

Затем добавьте is_expiring_productСтолбец , изначально заполненный пустыми строками:

df = df.assign(is_expiring_product='')

Вычисление ведро - результат вырезания дат в ячейки:

bucket = pd.cut(df.date, bins = [dMin, currDate - pd.offsets.Day(1),
    currDate, dMax], labels=['Past', 'Today', 'Future'])

Следующий шаг df_current - подмножество строк для «текущей» даты:

df_current = df[bucket == 'Today']

Для генерации индексов url s в строках для «других» дат,пробег:

ind_other = pd.Index(df[bucket.isin(['Past', 'Future'])].url.unique())

И последний шаг - создание значений для is_expiring_product и обновление этого столбца:

df.is_expiring_product.update((~df_current.url.isin(ind_other)).astype(int))

Результат:

        date  url is_expiring_product
0 2019-09-10  Xxx                    
1 2019-09-12  Xxx                   0
2 2019-09-14  Xxx                    
3 2019-09-11  Yyy                    
4 2019-09-12  Yyy                   0
5 2019-09-12  Zz1                   1
6 2019-09-12  Zz2                   1
7 2019-09-08  Ttt                    
8 2019-09-12  Ttt                   0
9 2019-09-15  Ttt                    

Описаниерезультаты:

  • Непустые значения для 2019-09-12 .
  • Индекс == 1 , 4 и 12 : 0 - Эти url s ( Xxx , Yyy и Ttt) присутствуют в другие даты.
  • Индекс == 5 и 6 : 1 - Эти url s ( Zz1 и Zz2 ) отсутствуют в другие даты.

Пример кода # 2: Вычисление is_expiring_product длявсе даты

Исходный DataFrame одинаков, начиная с определения dMin и dMax , как и раньше.

Затем определите следующую функцию, чтобы установитьсостояние истечения:

def setExpired(grp):
    currDate = grp.name    # Grouping key
    bucket = pd.cut(df.date, bins = [dMin, currDate - pd.offsets.Day(1),
        currDate, dMax], labels=['Past', 'Current', 'Future'])
    df_current = df[bucket == 'Current']
    ind_other = pd.Index(df[bucket.isin(['Past', 'Future'])].url.unique())
    res = (~df_current.url.isin(ind_other)).astype(int)
    return res

И все вычисления сводятся к одной инструкции, применяя этуФункция s для каждой группы (для каждой даты):

df['is_expiring_product'] = df.groupby('date').apply(setExpired).droplevel(0)

Требуется дополнительный вызов droplevel (0) , поскольку результат apply имеет MultiIndex с:

  • уровень 0 - даты группировки,
  • уровень 1 - ключи каждой серии , возвращаемой при вызовах setExpired .

Таким образом, чтобы сохранить его в df (используя "обычный" индекс), верхний уровень MultiIndex должен быть сброшен.

На этот раз результат:

        date  url  is_expiring_product
0 2019-09-10  Xxx                    0
1 2019-09-12  Xxx                    0
2 2019-09-14  Xxx                    0
3 2019-09-11  Yyy                    0
4 2019-09-12  Yyy                    0
5 2019-09-12  Zz1                    1
6 2019-09-12  Zz2                    1
7 2019-09-08  Ttt                    0
8 2019-09-12  Ttt                    0
9 2019-09-15  Ttt                    0

Еще одно, более простое и быстрое решение

Обратите внимание, что нет необходимости вычислять is_expiring_product для каждой строки отдельно.

Гораздо проще и быстрее, совершенно другое решение:

  • вычислить is_expiring_product для каждый URL (без повторов),
  • затем сохраните результат во всех строках, содержащих этот url .

Это решение работает следующим образом:

  • Группировать строки по url .
  • Для каждой группы:
    • принять дата столбец,
    • проверить, все лидаты совпадают,
    • , если есть, возврат 1 (этот url происходит только в single date),
    • в противном случае вернуть 0 (этот url встречается несколько дат).
  • "Развернуть" этот результат вкаждая строка, содержащая этот url .
  • Сохранить результат в столбце is_expiring_product .

Все вышеуказанное действие можно сохранить как single инструкция:

df['is_expiring_product'] = df.groupby('url').date\
    .transform(lambda grp: 1 if grp.unique().size == 1 else 0)

с тем же результатом, что и раньше.

Пожалуйста, напишите комментарий, сколько времени потребовалось для выполнения вышеуказанной инструкции в вашем DataFrame и каку него много рядов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...