2025, Dec 22 03:02

Как в pandas найти день перед простоем и получить накопленные дни в сети (с учётом хвоста)

Разбираем компактный способ в pandas выбрать строку перед простоем (переход 1→0) и получить накопленные дни в сети, включая хвост серии. Пример кода и пояснения

Подсчёт того, сколько времени оборудование остаётся в сети между простоями, прост, как только у вас есть бинарный поток статусов: 1 — работает, 0 — отключено. Сложность в том, чтобы выделить точный момент непосредственно перед каждым простоем вместе с накопленным числом дней в сети на этот момент. Ниже — компактный подход, который делает именно это и заодно учитывает хвостовой отрезок в конце набора данных.

Постановка задачи

Предположим, у вас есть DataFrame с бинарным столбцом статуса. Распространённый способ построить накопительный счётчик дней в сети между простоями — использовать группы, разбитые нулями, и нумеровать позиции внутри каждой группы.

import pandas as pd

# Пример построения накопительного счётчика между простоями (бинарный статус)
# 1 = в сети, 0 = простой
run_log = pd.DataFrame({"status_bit": [1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1]})

# Накопительный счётчик онлайн-отрезков, сгруппированных по нулям
run_log["uptime_ctr"] = (
    run_log["status_bit"]
        .groupby(run_log["status_bit"].eq(0).cumsum())
        .cumcount()
)

Для задачи извлечения мы используем небольшой набор данных, отражающий описанный сценарий и уже содержащий предварительно посчитанные накопительные итоги, выровненные по датам. Значения 0 в счётчике соответствуют дням простоя.

import pandas as pd

sample = {
    "date": [
        "5/29/2025", "5/31/2025", "6/1/2025", "6/2/2025",
        "6/3/2025", "6/4/2025", "6/5/2025", "6/6/2025",
        "6/7/2025", "6/8/2025", "6/9/2025", "6/10/2025"
    ],
    "status_bit": [1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1],
    "uptime_ctr": [1, 2, 3, 4, 0, 0, 0, 1, 2, 3, 4, 5]
}
events_df = pd.DataFrame(sample)
events_df["date"] = pd.to_datetime(events_df["date"])

Что именно нужно извлечь и почему это нетривиально

Цель — выбрать запись непосредственно перед каждым простоем, то есть последний день в каждом непрерывном отрезке единиц, и взять связанный с ним накопительный итог из столбца счётчика. Иными словами, нам нужна строка, где за 1 следует 0. Это выборка по границе: нас не интересуют строки самого простоя, только предшествующие им онлайн-дни, которые закрывают каждый отрезок. Есть и крайний случай: если набор данных заканчивается отрезком единиц и так и не падает до нуля, этот хвост тоже следует включить, поскольку он представляет последний непрерывный период работы в текущем срезе данных.

Решение

Определите индексы, где текущая строка равна 1, а следующая — 0, затем выберите эти строки и оставьте дату плюс накопительный счётчик. Наконец, если последняя строка равна 1, добавьте её, чтобы учесть хвостовой отрезок.

# Найти индексы, где последовательность из 1 переходит в 0 (день перед простоем)
cut_idx = events_df[
    (events_df["status_bit"] == 1) & (events_df["status_bit"].shift(-1) == 0)
].index

# Выбрать дату и накопительный счётчик на этих границах
summary_df = (
    events_df.loc[cut_idx, ["date", "uptime_ctr"]]
             .rename(columns={"uptime_ctr": "DaysOnline"})
)

# Включить последнюю строку, если серия заканчивается на 1 (хвостовой онлайн-отрезок)
if events_df["status_bit"].iloc[-1] == 1:
    tail = (
        events_df.iloc[[-1]][["date", "uptime_ctr"]]
                 .rename(columns={"uptime_ctr": "DaysOnline"})
    )
    summary_df = pd.concat([summary_df, tail])

print(summary_df)

Почему это работает

Извлечение опирается на точное выявление перехода 1 → 0. Отфильтровав строки, где текущий статус — 1, а следующий — 0, вы фиксируете границу, которая закрывает непрерывный онлайн-отрезок. Взяв накопительный счётчик в этих строках, вы получаете длину каждого только что завершившегося периода работы. Проверка последней строки гарантирует, что отрезок в конце набора не потеряется, когда за ним не следует простой, явно обозначающий границу.

Зачем это важно

Выборки по границам часто приводят к ошибкам «на один элемент». Захват строки перед простоем, а не самой строки простоя, сохраняет корректное определение «дней в сети между простоями». Учёт хвоста важен, когда наборы данных — это моментальные снимки, и они могут заканчиваться в то время, как оборудование всё ещё работает.

Итоги

Используйте чистый фильтр перехода 1→0, чтобы точно определить последний онлайн-день перед каждым простоем и взять накопительный счётчик именно там. Всегда явно обрабатывайте хвостовой случай, чтобы не потерять финальный непрерывный отрезок, если серия оканчивается на 1. Соблюдение этих двух деталей помогает избежать ошибок на границах и даёт точную сводку периодов работы.