Чистый python или itertools группируют список дат по разнице в днях между каждой датой

#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)]]