Python: выбор самой длинной последовательной серии дат в списке

#python #python-3.x #date #datetime #series

#python #python-3.x #Дата #дата-время #Серии

Вопрос:

У меня есть серия списков (на самом деле np.arrays), элементами которых являются даты.

 id
0a0fe3ed-d788-4427-8820-8b7b696a6033    [2019-01-30, 2019-01-31, 2019-02-01, 2019-02-0...
0a48d1e8-ead2-404a-a5a2-6b05371200b1    [2019-01-30, 2019-01-31, 2019-02-01, 2019-02-0...
0a9edba1-14e3-466a-8d0c-f8a8170cefc8    [2019-01-29, 2019-01-30, 2019-01-31, 2019-02-0...
Name: startDate, dtype: object
  

Для каждого элемента в серии (т. Е. Для каждого списка дат) я хочу сохранить самый длинный подсписок, в котором все даты являются последовательными. Я изо всех сил пытаюсь подойти к этому по-питоновски (простым / эффективным) способом. Единственный подход, который я могу придумать, — это использовать несколько циклов: перебирать значения серии (списки) и перебирать каждый элемент в списке. Затем я бы сохранил первую дату и количество последовательных дней и использовал временные значения для перезаписи результатов, если встречается более длинная последовательность последовательных дней. Однако это кажется крайне неэффективным. Есть ли лучший способ сделать это?

Комментарии:

1. преобразуйте даты в ординалы и получите самый длинный увеличивающийся подмассив. Я опубликовал ответ, который вы можете попробовать

Ответ №1:

Поскольку вы упомянули, что используете массивы дат numpy, имеет смысл придерживаться типов numpy вместо преобразования во встроенный тип. Я предполагаю, что ваши массивы имеют dtype ‘datetime64[D]’. В этом случае вы могли бы сделать что-то вроде

 import numpy as np

date_list = np.array(['2005-02-01', '2005-02-02', '2005-02-03',
       '2005-02-05', '2005-02-06', '2005-02-07', '2005-02-08', '2005-02-09',
       '2005-02-11', '2005-02-12',
       '2005-02-14', '2005-02-15', '2005-02-16', '2005-02-17',
       '2005-02-19', '2005-02-20',
       '2005-02-22', '2005-02-23', '2005-02-24',
       '2005-02-25', '2005-02-26', '2005-02-27', '2005-02-28'],
      dtype='datetime64[D]')

i0max, i1max = 0, 0
i0 = 0
for i1, date in enumerate(date_list):
    if date - date_list[i0] != np.timedelta64(i1-i0, 'D'):
        if i1 - i0 > i1max - i0max:
            i0max, i1max = i0, i1
        i0 = i1

print(date_list[i0max:i1max])

# output: ['2005-02-05' '2005-02-06' '2005-02-07' '2005-02-08' '2005-02-09']
  

Здесь i0 и i1 указывают начальные и конечные индексы текущего подмассива последовательных дат, а i0max и i1max начальные и конечные индексы самого длинного подмассива, найденного на данный момент. Решение использует тот факт, что разница между i -й и нулевой записью в списке последовательных дат составляет ровно i дней.

Ответ №2:

Вы можете преобразовать список в порядковые номера, которые увеличиваются для всех последовательных дат. Что означает next_date = previous_date 1 подробнее.

Затем найдите самый длинный последовательный подмассив.

Этот процесс займет O(n)->single loop время, что является наиболее эффективным способом получения этого.

код

 from datetime import datetime
def get_consecutive(date_list):
  # convert to ordinals
  v = [datetime.strptime(d, "%Y-%m-%d").toordinal()  for d in date_list]
  consecutive = []
  run = []
  dates = []

  # get consecutive ordinal sequence 
  for i in range(1, len(v)   1):
    run.append(v[i-1])
    dates.append(date_list[i-1])
    if i == len(v) or v[i-1]   1 != v[i]:
      if len(consecutive) < len(run):
        consecutive = dates
      dates = []
      run = []

  return consecutive
  

ВЫВОД:

 date_list = ['2019-01-29', '2019-01-30', '2019-01-31','2019-02-05']
get_consecutive(date_list )
# ordinales will be -> v = [737088, 737089, 737090, 737095]
OUTPUT:
['2019-01-29', '2019-01-30', '2019-01-31']
  

Теперь используйте get_consecutive in df.column.apply(get_consecutive) , это даст вам все увеличивающийся список дат. Или вы можете все функции для каждого списка, если вы используете какую-либо другую структуру данных.

Ответ №3:

Я собираюсь свести эту проблему к поиску последовательных дней в одном списке. Есть несколько приемов, которые делают его более питоническим, как вы просите. Следующий скрипт должен выполняться как есть. Я задокументировал, как это работает встроенным:

 from datetime import timedelta, date

# example input
days = [
    date(2020, 1, 1), date(2020, 1, 2), date(2020, 1, 4),
    date(2020, 1, 5), date(2020, 1, 6), date(2020, 1, 8),
]

# store the longest interval and the current consecutive interval
# as we iterate through a list
longest_interval_index = current_interval_index =  0
longest_interval_length = current_interval_length = 1

# using zip here to reduce the number of indexing operations
# this will turn the days list into [(2020-01-1, 2020-01-02), (2020-01-02, 2020-01-03), ...]
# use enumerate to get the index of the current day
for i, (previous_day, current_day) in enumerate(zip(days, days[1:]), start=1):
    if current_day - previous_day == timedelta(days= 1):
        # we've found a consecutive day! increase the interval length
        current_interval_length  = 1
    else:
        # nope, not a consecutive day! start from this day and start
        # counting from 1
        current_interval_index = i
        current_interval_length = 1
    if current_interval_length > longest_interval_length:
        # we broke the record! record it as the longest interval
        longest_interval_index = current_interval_index
        longest_interval_length = current_interval_length

print("Longest interval index:", longest_interval_index)
print("Longest interval: ", days[longest_interval_index:longest_interval_index   longest_interval_length])
  

Должно быть достаточно легко превратить это в функцию многократного использования.