Как определить циклы в данных временных рядов с перекрывающимися периодами?

#python #algorithm #graph #time-series #sequence

Вопрос:

Учитывая данные временных рядов с 4 категориями задач (A, B, C, D) и их соответствующими отметками времени, моя задача состоит в том,чтобы определить интервалы/циклы [(A,B,C, D)_1, (A,B,C, D)_2,…]

Это было бы просто (например, хэш-карта или связанный список) с чистыми, неперекрывающимися событиями, но мои данные содержат последовательности (отсортированные по времени), такие как [A, B, A, B, C, D, C, D]. Вот пример:

событие время
Задача А 11/1/16 3:57
Задача В 11/1/16 4:19
Задача А 11/1/16 7:43
Задача В 11/1/16 7:43
Задача C 11/1/16 7:51
Задача D 11/1/16 7:51
Задача C 11/1/16 8:11
Задача D 11/1/16 8:13
Задача А 11/3/16 3:49
Задача В 11/3/16 4:11
Задача В 11/3/16 7:34
Задача А 11/3/16 7:34
Задача C 11/3/16 7:43
Задача D 11/3/16 7:43
Задача C 11/3/16 8:03
Задача D 11/3/16 8:05
Задача А 11/5/16 3:41
Задача В 11/5/16 4:03
Задача А 11/5/16 7:26
Задача В 11/5/16 7:26
Задача D 11/5/16 7:35
Задача C 11/5/16 7:35
Задача C 11/5/16 7:54
Задача D 11/5/16 7:56

В этой ситуации правильным ответом было бы удалить «внутренние»/перекрывающиеся ABCD, как только задача A (начало цикла) уже началась. Это приводит к 3 периодам:

Задача А Задача В Задача C Задача D
11/1/16 3:57 11/1/16 4:19 11/1/16 8:11 11/1/16 8:13
11/3/16 3:49 11/3/16 4:11 11/3/16 8:03 11/3/16 8:05
11/5/16 3:41 11/5/16 4:03 11/5/16 7:54 11/5/16 7:56

Игнорируя (на данный момент) крайние случаи, такие как неполные последовательности событий, существует ли эффективный алгоритм для идентификации циклов при объединении внутренних периодов, которые перекрываются?

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

1. В примере, который вы показали, я бы просто использовал массив из 4 счетчиков. При сканировании входных данных увеличивайте счетчик, соответствующий каждой задаче. Когда все счетчики имеют одинаковое ненулевое значение, вы обнаружили конец группы перекрывающихся циклов.

Ответ №1:

Просто подход, о котором упоминал @user3386109, а также отслеживание меток времени событий.

Переместите входные данные в файл с именем events.txt .

 file = open("events.txt", "r")
result = []
partial_result = {}
max_count =0;
tasks_count = [0,0,0,0]
for event in file:
    event = event.strip('n')
    split_events = event.split()
    max_count = max(tasks_count)
    if len(split_events)==4: #Task data
        task_name = split_events[1]
        time =  split_events[2] " " split_events[3]
        idx = ord(task_name)-65
        curr_count = tasks_count[idx]
        if (curr_count==max_count or curr_count 1 == max_count) and task_name not in partial_result:
            partial_result[task_name] = time
        tasks_count[idx]  =1

    if len(partial_result)==4:
        result.append(partial_result)
        partial_result ={}
        tasks_count = [0,0,0,0]

print(result)
 

Заключительный Вывод

 [{'A': '11/1/16 3:57', 'B': '11/1/16 4:19', 'C': '11/1/16 8:11', 'D': '11/1/16 8:13'}, {'A': '11/3/16 3:49', 'B': '11/3/16 4:11', 'C': '11/3/16 8:03', 'D': '11/3/16 8:05'}, {'A': '11/5/16 3:41', 'B': '11/5/16 4:03', 'C': '11/5/16 7:54', 'D': '11/5/16 7:56'}]
 

Ответ №2:

Вы можете использовать collections.defaultdict :

 import collections, datetime, re
r, d = [], collections.defaultdict(list)
data = [['Task A', '11/1/16 3:57'], ['Task B', '11/1/16 4:19'], ['Task A', '11/1/16 7:43'], ['Task B', '11/1/16 7:43'], ['Task C', '11/1/16 7:51'], ['Task D', '11/1/16 7:51'], ['Task C', '11/1/16 8:11'], ['Task D', '11/1/16 8:13'], ['Task A', '11/3/16 3:49'], ['Task B', '11/3/16 4:11'], ['Task B', '11/3/16 7:34'], ['Task A', '11/3/16 7:34'], ['Task C', '11/3/16 7:43'], ['Task D', '11/3/16 7:43'], ['Task C', '11/3/16 8:03'], ['Task D', '11/3/16 8:05'], ['Task A', '11/5/16 3:41'], ['Task B', '11/5/16 4:03'], ['Task A', '11/5/16 7:26'], ['Task B', '11/5/16 7:26'], ['Task D', '11/5/16 7:35'], ['Task C', '11/5/16 7:35'], ['Task C', '11/5/16 7:54'], ['Task D', '11/5/16 7:56']]
for a, b in data: 
   v = list(map(int, re.findall('d ', b)))
   _date = datetime.datetime(v[2], v[0], v[1], v[-2], v[-1], 0)
   if (k:=a.split()[-1]) == 'A' and all(j in d for j in ['A', 'B', 'C', 'D']):
       r.append(d)
       d = collections.defaultdict(list)
       d[k].append(_date)
   else:
       d[k].append(_date)

r.append(d)

f, f1 = {'A':min, 'B':min, 'C':max, 'D':max}, lambda x:f'{x.month}/{x.day}/{x.year} {x.hour}:{str(x.minute).zfill(2)}'
result = [{a:f1(f[a](b)) for a, b in i.items()} for i in r]
 

Выход:

 [{'A': '11/1/16 3:57', 'B': '11/1/16 4:19', 'C': '11/1/16 8:11', 'D': '11/1/16 8:13'}, 
 {'A': '11/3/16 3:49', 'B': '11/3/16 4:11', 'C': '11/3/16 8:03', 'D': '11/3/16 8:05'}, 
 {'A': '11/5/16 3:41', 'B': '11/5/16 4:03', 'C': '11/5/16 7:54', 'D': '11/5/16 7:56'}]
 

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

1. Спасибо! defaultdict Подход @Ajax1234 кажется более устойчивым к отсутствующим значениям и неполным циклам. Попробовал встречный подход @Maruthi Adithya и @user3386109, но пришлось добавить много операторов if-else для обработки таких крайних случаев.