Найти какие-нибудь праздники между двумя датами в большом наборе данных? - PullRequest
1 голос
/ 08 июля 2019

Я работаю над набором данных, который имеет около 26 миллионов строк и 13 столбцов, включая два столбца datetime arr_date и dep_date. Я пытаюсь создать новый логический столбец, чтобы проверить, есть ли какие-либо праздники в США между этими датами. Я использую функцию apply для всего кадра данных, но время выполнения слишком медленное. Код работает уже более 48 часов на облачной платформе Goolge (оперативная память 24 ГБ, 4 ядра). Есть ли более быстрый способ сделать это?

Набор данных выглядит следующим образом: Пример данных

Я использую код -

import pandas as pd
import numpy as np
from pandas.tseries.holiday import USFederalHolidayCalendar as calendar

df = pd.read_pickle('dataGT70.pkl')
cal = calendar()
def mark_holiday(df):
    df.apply(lambda x: True if (len(cal.holidays(start=x['dep_date'], end=x['arr_date']))>0 and x['num_days']<20) else False, axis=1)
    return df

df = mark_holiday(df)

Ответы [ 2 ]

1 голос
/ 08 июля 2019

Вы уже рассматривали возможность использования pandas.merge_asof для этого?

Я мог бы представить, что map и apply с лямбда-функциями не могут быть выполнены так эффективно.

ОБНОВЛЕНИЕ: Ах, извините, я только что прочитал, что вам нужно только логическое значение, если между ними есть какие-то праздники, это делает это намного проще.Если этого достаточно, вам просто нужно выполнить шаги 1-5, а затем сгруппировать DataFrame, который является результатом шага 5, по дате начала / окончания и использовать count в качестве статистической функции, чтобы количество выходных в диапазонах.Этот результат можно присоединить к исходному набору данных, аналогично шагу 8, описанному ниже.Затем заполните остальные значения fillna(0).Сделайте что-то вроде joined_df['includes_holiday']= joined_df['joined_count_column']>0.После этого вы можете, если хотите, снова удалить joined_count_column из своего DataFrame.

Если вы используете pandas_merge_asof, вы можете выполнить эти шаги (шаги 6 и 7 необходимы только в том случае, если вам нужно иметьвсе выходные между началом и концом также в вашем DataFrame результата, а не только в логических значениях):

  1. Загрузите записи вашего отпуска в DataFrame и проиндексируйте их по дате.Праздники должны быть по одной дате в строке (сохранение диапазонов, например, на Рождество с 24 по 26 в один ряд, сделало бы это намного более сложным).
  2. Создайте копию вашего фрейма данных, используя только столбцы даты начала и окончания.ОБНОВЛЕНИЕ: каждое начало, дата окончания должны встречаться только один раз.Например, используя groupby.
  3. Используйте merge_asof с разумным значением допуска (если вы присоединяетесь в начале периода, используйте direction='forward', если вы используете дату окончания, используйте direction='backward' и how='inner'.
  4. В результате у вас есть объединенный DataFrame с начальным, конечным столбцами и столбцом даты из вашего кадра данных выходных. Вы получаете только записи, для которых был найден выходной с заданным допуском, но позже выМожно объединить эти данные с исходным DataFrame. Возможно, теперь у вас будут дубликаты исходных записей.
  5. Затем проверьте объединенные выходные для ваших записей с индексаторами, сравнив их со столбцом начала и конца, и удалите выходные., которые не являются промежуточными.
  6. Сортируйте кадр данных, полученный вами на шаге 5 (используйте что-то вроде df.sort_values(['start', 'end', 'holiday'], inplace=True). Теперь вам нужно вставить числовой столбец, который нумерует выходные дни между периодами (те, которые вы получили после шага 5) от 1 до ... (для каждого периода, начинающегося с 1). Необходимо использовать unstack на следующем шаге дляполучите выходные данные в столбцах.
  7. Добавьте индекс в свой фрейм данных на основе даты начала периода, даты окончания периода и столбца подсчета, который вы вставили в шаге 6. Используйте df.unstack(level=-1) в фрейме данных, который вы подготовили в шагах 1-7.Теперь у вас есть сжатый DataFrame с вашими исходными периодами и праздничными днями, расположенными по столбцам.
  8. Теперь вам нужно только объединить этот DataFrame с вашими исходными данными, используя original_df.merge(df_from_step7, left_on=['start', 'end'], right_index=True, how='left')

Результатом этого является файл с вашими исходными данными, содержащий диапазоны дат, и для каждого диапазона дат выходные дни, которые лежат между периодами, хранятся в отдельных столбцах, каждый за данными.Грубо говоря, нумерация на шаге 6 назначает праздничные дни столбцам и приводит к тому, что праздничные дни всегда назначаются столбцам справа налево (у вас не будет выходных в столбце 3, если столбец 1 пуст).

Шаг 6., вероятно, также немного сложен, но вы можете сделать это, например, добавив серию, заполненную диапазоном, а затем исправив ее, чтобы нумерация начиналась с 0 или 1 в каждой группе с помощью shift или группировка по началу, завершается aggregate({'idcol':'min') и присоединяется к результату обратно, чтобы вычесть его из значения, назначенного в диапазоне-последовательности.

В целом, я думаю, что это звучит сложнее, чем есть, и этодолжно быть выполнено довольно эффективно.Особенно, если ваши периоды не так велики, потому что после шага 5 ваш результирующий набор должен быть намного меньше исходного фрейма данных, но даже если это не так, он все равно должен быть достаточно эффективным, поскольку он может использовать скомпилированный код.

0 голосов
/ 08 июля 2019

Мне потребовалось около двух минут, чтобы запустить примерный кадр данных из 30-метровых строк с двумя столбцами start_date и end_date.

Идея состоит в том, чтобы получить отсортированный список всех праздников, происходящих в или после минимальной даты начала, а затем использовать bisect_left из модуля bisect, чтобы определить следующий праздник, наступающий в или после каждой даты начала. Этот праздник затем сравнивается с датой окончания. Если она меньше или равна дате окончания, то должен быть хотя бы один выходной в диапазоне дат между датами начала и окончания (оба включительно).

from bisect import bisect_left
import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar as calendar

# Create sample dataframe of 10k rows with an interval of 1-19 days.
np.random.seed(0)
n = 10000  # Sample size, e.g. 10k rows.
years = np.random.randint(2010, 2019, n)
months = np.random.randint(1, 13, n)
days = np.random.randint(1, 29, n)
df = pd.DataFrame({'start_date': [pd.Timestamp(*x) for x in zip(years, months, days)],
                   'interval': np.random.randint(1, 20, n)})
df['end_date'] = df['start_date'] + pd.TimedeltaIndex(df['interval'], unit='d')
df = df.drop('interval', axis=1)

# Get a sorted list of holidays since the fist start date.
hols = calendar().holidays(df['start_date'].min())

# Determine if there is a holiday between the start and end dates (both inclusive).
df['holiday_in_range'] = df['end_date'].ge(
    df['start_date'].apply(lambda x: bisect_left(hols, x)).map(lambda x: hols[x]))

>>> df.head(6)
  start_date   end_date  holiday_in_range
0 2015-07-14 2015-07-31             False
1 2010-12-18 2010-12-30              True  # 2010-12-24
2 2013-04-06 2013-04-16             False
3 2013-09-12 2013-09-24             False
4 2017-10-28 2017-10-31             False
5 2013-12-14 2013-12-29              True  # 2013-12-25

Таким образом, для данной start_date отметки времени (например, 2013-12-14), bisect_right(hols, '2013-12-14') даст 39, а hols [39] приведет к 2013-12-25, следующий выходной приходится на или после даты начала 2013-12-14 , Следующий праздник рассчитывается как df['start_date'].apply(lambda x: bisect_left(hols, x)).map(lambda x: hols[x]). Затем этот выходной сравнивается с end_date, и, таким образом, holiday_in_range равен True, если end_date больше или равно значению этого выходного, в противном случае выходной должен падать после этого end_date.

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