Пример кода # 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 и каку него много рядов.