#python #pandas #algorithm #itertools #more-itertools
Вопрос:
Наличие списка заказанных дат:
[
datetime.date(2006, 8, 15),
datetime.date(2006, 9, 12),
datetime.date(2007, 8, 10),
datetime.date(2021, 4, 6),
datetime.date(2021, 4, 16),
datetime.date(2021, 4, 19)
...
]
Я хотел бы иметь группы, содержащие даты, которые составляют максимум 30 дней между всеми датами (расстояние между первым элементом группы и последним из этих групп будет <= 30 дней).
Например, используя предыдущий список, я получу:
- group_1 = [дата и время. дата(2006, 8, 15), дата и время.дата(2006, 9, 12)] (даты
- group_2 = [дата и время.дата(2021, 4, 6), дата и время.дата(2021, 4, 16), дата и время.дата(2021, 4, 19)]
- group_3 = [дата и время.дата(2007, 8, 10)] (другие даты не связаны
Я попытался использовать iter-инструменты groupby, но ключевая функция не позволяет сравнивать 2 даты,например «лямбда x, y: (x-y).дней <= 30….» Я не знаю, могу ли я использовать groupby для решения этой проблемы, или мне нужна другая функция itertools. Я знаю, что мог бы построить для этого алгоритм python, но я думаю, что будет существовать простой вариант решения этой проблемы, но я его не нашел 🙁
Спасибо!
Комментарии:
1. 2021-05-07 — в течение 30 дней с 2021-04-19. Не следует ли объединить группы 4 и 2?
2. ДА. Извините за ошибку, опубликовав результат. обновленный. Спасибо
3. @Шайдо Моя интерпретация словосочетания «группы, содержащие даты, которые не превышают 30 дней между всеми датами», заключается в том, что последняя дата группы должна быть не более чем на 30 дней позже первой даты группы, поэтому 2021-05-07 не относится ко второй группе, потому что она на 31 день позже 2021-04-06.
4. @blhsing ваша интерпретация верна. Каждый первый элемент группы и последний элемент этой группы будут Таким образом, цель состоит в том, чтобы создать группы дат в определенном диапазоне
Ответ №1:
itertools.groupby
предназначен для группировки элементов, которые не зависят друг от друга. В вашем случае проще и понятнее просто повторить список дат, чтобы построить список групп, вставив новую группу, когда текущая дата более чем на 30 дней позже первой даты текущей группы:
dates = [
datetime.date(2006, 8, 15),
datetime.date(2006, 9, 12),
datetime.date(2007, 8, 10),
datetime.date(2021, 4, 6),
datetime.date(2021, 4, 16),
datetime.date(2021, 4, 19),
datetime.date(2021, 5, 7)
]
threshold = datetime.timedelta(30)
groups = []
for date in dates:
if not groups or date - group[0] > threshold:
group = []
groups.append(group)
group.append(date)
groups
стал бы:
[[datetime.date(2006, 8, 15), datetime.date(2006, 9, 12)],
[datetime.date(2007, 8, 10)],
[datetime.date(2021, 4, 6), datetime.date(2021, 4, 16), datetime.date(2021, 4, 19)],
[datetime.date(2021, 5, 7)]]
Комментарии:
1. «itertools.groupby предназначен для группировки элементов, которые не зависят друг от друга» спасибо за это разъяснение. Иногда вам кажется, что вы можете выполнить задачу с помощью инструмента, и вы фокусируетесь только на этом инструменте. Моя ошибка. Спасибо
Ответ №2:
Вот pandas
решение, которое сравнивает даты со следующей датой и проверяет, есть ли между ними 30 дней. Затем он присваивает номер группы с cumsum
:
import pandas as pd
import datetime
data = [ datetime.date(2006, 8, 15), datetime.date(2006, 9, 12), datetime.date(2007, 8, 10), datetime.date(2021, 4, 6), datetime.date(2021, 4, 16), datetime.date(2021, 4, 19)]
df = pd.DataFrame(data, columns=['date'])
df['groups'] = (df['date'].diff() > pd.Timedelta(30, unit='D')).cumsum()
Выход:
Дата | Группы | |
---|---|---|
0 | 2006-08-15 | 0 |
1 | 2006-09-12 | 0 |
2 | 2007-08-10 | 1 |
3 | 2021-04-06 | 2 |
4 | 2021-04-16 | 2 |
5 | 2021-04-19 | 2 |
Или если вам просто нужен список списков в качестве вывода: df.groupby((df['date'].diff() > pd.Timedelta(30, unit='D')).cumsum()).agg(list)['date'].to_list()
Комментарии:
1. Также , возможно, посмотрим
pd.Series.diff
, что может привести в порядок связанные с этим вычисленияshift
.2. @TMBailey, спасибо. Гораздо более чистый код, подобный этому.
Ответ №3:
Итерационное решение с простым старым циклом for в этом случае довольно простое.
Я не думаю, что это будет легко или эффективно использовать itertools
для решения этой проблемы, поскольку группировка в этом случае зависит от контекста данных, что, вероятно, приведет к решению O(N^2), тогда как итеративный подход-O(N).
dts = [
datetime.date(2006, 8, 15),
datetime.date(2006, 9, 12),
datetime.date(2007, 8, 10),
datetime.date(2021, 4, 6),
datetime.date(2021, 4, 16),
datetime.date(2021, 4, 19)
]
def groupDateTimes(dts):
i = 0
ans = []
group = []
delta30days = datetime.timedelta(days=30)
while i < len(dts):
cur = dts[i]
if not group:
group.append(cur)
elif cur - group[0] <= delta30days:
group.append(cur)
else:
ans.append(group)
group = [cur]
i = 1
if group:
ans.append(group)
return ans
print(groupDateTimes(dts)) // [[datetime.date(2006, 8, 15), datetime.date(2006, 9, 12)], [datetime.date(2007, 8, 10)], [datetime.date(2021, 4, 6), datetime.date(2021, 4, 16), datetime.date(2021, 4, 19)]]