pandas: расчет дат начала и времени выполнения задач

Я решил распределить выполнение определенных задач по дням в зависимости от времени, затрачиваемого на задачи. В день выделяется 7 часов, задачи же могут занимать разное время, но только в целых числах (1 ч, 2 ч, 3 ч и т.д.).

У меня имеется data frame, где содержатся названия задач, дата начала, дата конца и время выполнения задачи. Как автоматически заполнить даты в data frame? Уже десятки вариантов перебрал.

Основная трудность для меня состоит в том, чтобы учитывать время задач. Например, длится она 16 часов. Значит, на ее выполнение понадобится 2 полных дня (по 7 часов) и 2 часа от третьего дня. Тогда для третьего дня остается свободных 5 часов, в которые можно начать работу над следующей задачей.

Думаю собрать словарь, где ключами выступает название задачи, а значениями - даты в зависимости от того, сколько длится одна задача. Словарь можно собрать из 2 списков, в которых хранятся даты и время на задачи. Возможно, думаю не в ту сторону, но голова уже отказывается выдавать решения, прошу помочь)

Так выглядит data frame:

                                              Title Start Date End Date  Description
1                                          Handover                               16
2                                     Project Setup                                8
3  Add localization two languages (English, Spanish)                               6

Ответы (1 шт):

Автор решения: strawdog

Не совсем понятно, но если вам просто нужно получить конечную дату выполнения задач, имея начальную дату и количество часов, отпущенных на каждый день, то можно сделать так:

import pandas as pd

hours_per_day = 7

df = pd.DataFrame({"task":["task1", "task2", "task3"], 
                   "start date":["2021-07-02", "2021-07-01", "2021-07-03"],
                   "duration":[16, 8, 6]})

df["start date"] = pd.to_datetime(df["start date"])

получаем исходный датафрейм:

    task start date  duration
0  task1 2021-07-02        16
1  task2 2021-07-01         8
2  task3 2021-07-03         6

дальше просто вычисляем дельту:

df["end date"] = df["start date"] + pd.to_timedelta(df["duration"]//hours_per_day, "d")

df:

    task start date  duration   end date
0  task1 2021-07-02        16 2021-07-04
1  task2 2021-07-01         8 2021-07-02
2  task3 2021-07-03         6 2021-07-03

UPDATE

после уточнения автора вопроса, выяснилось, что по сути необходимо построить последовательный work-flow для непрерывного выполнения задач. Это можно сделать как-нибудь так:

import numpy as np
import pandas as pd

# исходные данные
durations = [4, 8, 2, 16, 5]
hours_per_day = 7
start_date = "2021-07-01"

arr = np.array(durations)
bins = [0]
bins.extend([x[-1] for x in 
             np.array_split(np.arange(np.sum(arr)), np.ceil(np.sum(arr)/hours_per_day))])
# [0, 6, 13, 20, 27, 34]
totals = np.cumsum(arr)
# [ 4 12 14 30 35]
idx = np.digitize(totals, bins, right=True)-1
# [0 1 2 4 5]
start_deltas = np.pad(idx, (1, 0))[:-1]
# [0 0 1 2 4] <-- искомые дельты

Далее конструируем датафрейм:

df = pd.DataFrame({"task":[f"task{x}" for x in range(len(durations))],
                   "duration": durations,
                   "delta":start_deltas})
df["start_date"] = start_date
df["start_date"] = pd.to_datetime(df["start_date"])
df["start_date"] = df["start_date"] + pd.to_timedelta(df["delta"], "d")
df["end_date"] = df["start_date"] + pd.to_timedelta(df["duration"]//hours_per_day, "d")

получаем df:

    task  duration  delta start_date   end_date
0  task0         4      0 2021-07-01 2021-07-01
1  task1         8      0 2021-07-01 2021-07-02
2  task2         2      1 2021-07-02 2021-07-02
3  task3        16      2 2021-07-03 2021-07-05
4  task4         5      4 2021-07-05 2021-07-05

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

UPDATE 2*

Если нужно из workflow исключить нерабочие дни, то можно сделать так:

from pandas.tseries.offsets import *

# вместо последних двух преобразований сделать:
df["start_date"] = df.apply(lambda x: x["start_date"] + BDay(pd.to_timedelta(x["delta"], "d").days), axis=1)
df["end_date"] = df.apply(lambda x: x["start_date"] + BDay(pd.to_timedelta(x["duration"]//hours_per_day, "d").days), axis=1)

получим df:

    task  duration  delta start_date   end_date
0  task0         4      0 2021-07-01 2021-07-01
1  task1         8      0 2021-07-01 2021-07-02
2  task2         2      1 2021-07-02 2021-07-02
3  task3        16      2 2021-07-05 2021-07-07
4  task4         5      4 2021-07-07 2021-07-07
→ Ссылка