Создание изменений разрыва с течением времени (дата начала и окончания)

Я все еще новичок в питонах. Я пытаюсь создать код Python, чтобы получить дату извлечения, в которой впервые произошел разрыв, как start_time. Когда условие становится «Y», я хотел бы создать end_date на основе условия и заполнить дату извлечения, в которой оно стало «Y». В то же время я хочу сверить его с предыдущими датами, прежде чем указывать дату окончания. Например, гэп открылся в январе и закрылся в феврале, но вновь открылся в марте и закрылся в апреле. В целом, я хочу проводить непрерывную проверку на случай, если в будущем добавлю больше данных.

Пример таблицы данных:

ИДЕНТИФИКАТОР суб_ид Состояние дата_извлечения 100 х Н 15 января 2024 г. 100 х Да 2024-02-01 100 й Да 2024-02-01 101 я Н 2024-02-01 101 я Н 2024-03-16 100 х Н 2024-03-16 100 х Да 2024-04-10 101 я Н 2024-04-10 101 я Да 15 мая 2024 г. 102 ш Н 15 мая 2024 г. 102 ш Да 2024-06-15

В целом, это то, что я ожидаю:

Пример повторного открытия пробела происходит для идентификатора 100 с sub_id, равным x, тогда как непрерывный разрыв происходит для идентификатора 101 с sub_id, равным z.

ИДЕНТИФИКАТОР суб_ид Состояние дата_извлечения Дата начала конечная_дата 100 х Н 15 января 2024 г. 15 января 2024 г. Н/Д 100 х Да 2024-02-01 15 января 2024 г. 2024-02-01 100 й Да 2024-02-01 2024-02-01 2024-02-01 101 я Н 2024-02-01 2024-02-01 Н/Д 101 я Н 2024-03-16 2024-02-01 Н/Д 100 х Н 2024-03-16 2024-03-16 Н/Д 100 х Да 2024-04-10 2024-03-16 2024-04-10 101 я Н 2024-04-10 2024-02-01 Н/Д 101 я Да 15 мая 2024 г. 2024-02-01 15 мая 2024 г. 102 ш Н 15 мая 2024 г. 15 мая 2024 г. Н/Д 102 ш Да 2024-06-15 15 мая 2024 г. 2024-06-15

🤔 А знаете ли вы, что...
В Python есть инструменты для тестирования кода, такие как библиотека unittest.


1
91
3

Ответы:

Пытаться:

def fn(x):
    start_dates, end_dates = [], []

    start_date = None
    for c, d in zip(x["Condition"], x["extraction_date"]):
        if start_date is None:
            start_date = d

        start_dates.append(start_date)

        if c == "Y":
            end_dates.append(d)
            start_date = None
        else:
            end_dates.append(None)

    x["start_date"] = start_dates
    x["end_dates"] = end_dates

    return x


df = (
    df.groupby(["ID", "sub_id"], group_keys=True)
    .apply(fn, include_groups=False)
    .sort_index(level=2)
    .droplevel(2)
    .reset_index()
)

print(df)

Распечатки:

     ID sub_id Condition extraction_date  start_date   end_dates
0   100      x         N      2024-01-15  2024-01-15        None
1   100      x         Y      2024-02-01  2024-01-15  2024-02-01
2   100      y         Y      2024-02-01  2024-02-01  2024-02-01
3   101      z         N      2024-02-01  2024-02-01        None
4   101      z         N      2024-03-16  2024-02-01        None
5   100      x         N      2024-03-16  2024-03-16        None
6   100      x         Y      2024-04-10  2024-03-16  2024-04-10
7   101      z         N      2024-04-10  2024-02-01        None
8   101      z         Y      2024-05-15  2024-02-01  2024-05-15
9   102      w         N      2024-05-15  2024-05-15        None
10  102      w         Y      2024-06-15  2024-05-15  2024-06-15

Вот способ решить вашу проблему и узнать, когда каждый "gap" начинается (Condition == 'N') и когда заканчивается (Condition == 'Y'). Я придерживаюсь базового Python, не полагаясь на панды, которые действительно были бы лучшим выбором во многих сценариях с реальными данными.

Шаг 1: Сначала импортируйте дату и время для обработки этих дат. (вы не хотите возиться со строками даты)

from datetime import datetime

Шаг 2: Вот ваши данные в виде списка словарей (похож на JSON, но на Python).

data = [
    {'ID': 100, 'sub_id': 'x', 'Condition': 'N', 'extraction_date': '2024-01-15'},
    {'ID': 100, 'sub_id': 'x', 'Condition': 'Y', 'extraction_date': '2024-02-01'},
    {'ID': 100, 'sub_id': 'y', 'Condition': 'Y', 'extraction_date': '2024-02-01'},
    {'ID': 101, 'sub_id': 'z', 'Condition': 'N', 'extraction_date': '2024-02-01'},
    {'ID': 101, 'sub_id': 'z', 'Condition': 'N', 'extraction_date': '2024-03-16'},
    {'ID': 100, 'sub_id': 'x', 'Condition': 'N', 'extraction_date': '2024-03-16'},
    {'ID': 100, 'sub_id': 'x', 'Condition': 'Y', 'extraction_date': '2024-04-10'},
    {'ID': 101, 'sub_id': 'z', 'Condition': 'N', 'extraction_date': '2024-04-10'},
    {'ID': 101, 'sub_id': 'z', 'Condition': 'Y', 'extraction_date': '2024-05-15'},
    {'ID': 102, 'sub_id': 'w', 'Condition': 'N', 'extraction_date': '2024-05-15'},
    {'ID': 102, 'sub_id': 'w', 'Condition': 'Y', 'extraction_date': '2024-06-15'},
]

Шаг 3: Преобразуйте строки Extract_date в объекты DateTime.

for entry in data:
    entry['extraction_date'] = datetime.strptime(entry['extraction_date'], '%Y-%m-%d')

шаг 4: Используйте словарь start_tracker, чтобы отслеживать, где начинается и заканчивается пробел.

start_tracker = {}

Шаг 5: Просмотрите каждую запись и выясните, начинаете ли вы новый пробел, завершаете существующий или просто расслабляетесь между ними.

for entry in data:
    key = (entry['ID'], entry['sub_id'])
    
    if entry['Condition'] == 'N':
        # new gap or continuation of existing gap
        if key not in start_tracker or start_tracker[key]['end_date'] is not None:
            # start new gap
            start_tracker[key] = {'start_date': entry['extraction_date'], 'end_date': None}
        # Update current entry with the start date
        entry['start_date'] = start_tracker[key]['start_date']
        entry['end_date'] = None
    
    elif entry['Condition'] == 'Y':
        # end gap
        if key in start_tracker and start_tracker[key]['end_date'] is None:
            # update end date for this gap
            start_tracker[key]['end_date'] = entry['extraction_date']
            entry['start_date'] = start_tracker[key]['start_date']
            entry['end_date'] = entry['extraction_date']
        else:
            # Edege case handling (not expected in your data)
            entry['start_date'] = None
            entry['end_date'] = entry['extraction_date']
    else:
        # if we get an unexpected condition then we handle it gracefully
        entry['start_date'] = None
        entry['end_date'] = None

Шаг 6: Отобразите результаты

print("ID", "sub_id", "Condition", "extraction_date", "start_date", "end_date", sep = "\t")
for entry in data:
    print(
        entry['ID'], entry['sub_id'], entry['Condition'],
        entry['extraction_date'].strftime('%Y-%m-%d'),
        entry['start_date'].strftime('%Y-%m-%d') if entry['start_date'] else "N/A",
        entry['end_date'].strftime('%Y-%m-%d') if entry['end_date'] else "N/A", sep = "\t"
    )

Запуск кода даст вам:

(venv) PS C:\Users\xxxxxxx\xxxx\src> python .\test.py

ID      sub_id  Condition       extraction_date start_date      end_date
100     x       N       2024-01-15      2024-01-15      N/A
100     x       Y       2024-02-01      2024-01-15      2024-02-01
100     y       Y       2024-02-01      N/A     2024-02-01
101     z       N       2024-02-01      2024-02-01      N/A
101     z       N       2024-03-16      2024-02-01      N/A
100     x       N       2024-03-16      2024-03-16      N/A
100     x       Y       2024-04-10      2024-03-16      2024-04-10
101     z       N       2024-04-10      2024-02-01      N/A
101     z       Y       2024-05-15      2024-02-01      2024-05-15
102     w       N       2024-05-15      2024-05-15      N/A
102     w       Y       2024-06-15      2024-05-15      2024-06-15

Решено

Отсортируйте значения по идентификатору и sub_id и используйте условие «Условие == Y» для создания групп. После этого сгруппируйте и используйте преобразование, чтобы получить даты для столбцов. Используйте панды, где с первым условием (Условие == Y) удалите ненужные даты.

m = df.sort_values(by=['ID', 'sub_id'])['Condition'].eq('Y')
g = m[::-1].cumsum().sort_index()
grp = df.groupby(['ID', 'sub_id', g])['extraction_date']

df['start_date'] = grp.transform('min')
df['end_date'] = grp.transform('max').where(m)

Конечный результат:

 ID sub_id Condition extraction_date      start        End
100      x         N      2024-01-15 2024-01-15        NaT
100      x         Y      2024-02-01 2024-01-15 2024-02-01
100      y         Y      2024-02-01 2024-02-01 2024-02-01
101      z         N      2024-02-01 2024-02-01        NaT
101      z         N      2024-03-16 2024-02-01        NaT
100      x         N      2024-03-16 2024-03-16        NaT
100      x         Y      2024-04-10 2024-03-16 2024-04-10
101      z         N      2024-04-10 2024-02-01        NaT
101      z         Y      2024-05-15 2024-02-01 2024-05-15
102      w         N      2024-05-15 2024-05-15        NaT
102      w         Y      2024-06-15 2024-05-15 2024-06-15

Обновлено: слегка измененная версия на случай, если группы формируются неправильно с использованием первого решения.

m = df['Condition'].eq('Y')
g = (df.groupby(['ID', 'sub_id'])['Condition']
     .transform(lambda g: g[::-1].eq('Y').cumsum())
     .to_numpy() # or reset_index(drop=True)
    )

grps = df.groupby(['ID', 'sub_id', g])['extraction_date']

df['start_date'] = grps.transform('min')
df['end_date'] = grps.transform('max').where(m)