Правильный сброс данных, которые в Python должны строго увеличиваться с течением времени

Я анализирую данные счетчиков часов работы набора машин. Когда машина включена, счетчик часов увеличивается, в противном случае он остается прежним. Для всех машин я должен ожидать только увеличения показателей счетчика моточасов. Однако бывают случаи, когда обновление программного обеспечения (или другая причина, не зависящая от меня) приводит к сбросу счетчика часов в любой момент времени и возобновлению передачи данных счетчика часов с 0 (обратите внимание, что, судя по тому, что я видел, он сбрасывается на 0, а не какое-то другое число).

У меня есть данные в фрейме данных pandas. Ключевыми столбцами являются «Serial» (серийный номер машины) и «Часы». Я написал несколько функций, которые проверяют значение за значением, но это занимает слишком много времени (у меня 8 миллионов строк и 3000 уникальных машин).

Я хотел бы создать функцию, которая корректирует данные часов, регулируя смещения, вызванные «сбросами».

Вот код для воссоздания образца фрейма данных:

df = pd.DataFrame()

df['Serial'] = (pd.Series(['Unit1', 'Unit1', 'Unit1', 'Unit1', 'Unit1', 'Unit1', 'Unit1', 'Unit1', 'Unit1', 'Unit1', 'Unit1', 'Unit1',
                           'Unit2', 'Unit2', 'Unit2', 'Unit2', 'Unit2', 'Unit2', 'Unit2', 'Unit2', 'Unit2', 'Unit2', ]).astype('category'))

df['Hours'] = pd.Series([3, 4, 6, 2, 4, 6, 1, 2, 3, 4, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).astype('int16')

df['Diff'] = df.groupby(['Serial'], observed=True)['Hours'].diff().fillna(0)

После нескольких итераций я остановился на этой функции как средстве исправления смещений:

def align_resets(group, hours_col, diff_col):
    hours = group[hours_col]
    diff = group[diff_col]
    
    group_starts = diff[diff < 0].index
    groups = np.split(hours, group_starts)
    n_groups = len(groups)
    
    if n_groups == 1:
        return hours
        
    else:
        reset_series = groups[0]
        
        for i in range(1, n_groups):
            last_entry = reset_series.iloc[-1]
            adjusted_group = last_entry + groups[i]
            reset_series = pd.concat([reset_series, adjusted_group]).reset_index(drop=True)
            
        return reset_series

Я использую groupby и apply, чтобы вернуть столбец «Исправлено» в моем фрейме данных следующим образом:

df['Corrected'] = df.groupby('Serial', observed=True).apply(lambda group: align_resets(group, 'Hours', 'Diff')).reset_index(level=0, drop=True)

Кажется, код работает. Он использует np.split, чтобы найти индексные числа, в которых происходит отрицательное изменение часов, а затем пересчитывает, какими должны быть «Часы».

Я не пробовал это на своем реальном наборе данных, но хотел посмотреть, есть ли более элегантное решение проблемы.

По моему мнению, должна быть четкая логика, чтобы понять, что машина была перезагружена. В 3->4->6->26 -> 2 явно сбросился, но у нас нет оснований полагать, что 3 -> 4 тоже не сбросился.

Panda Kim 29.05.2024 02:44

@PandaKim согласился. Однако частота отчетов такова, что после отдыха будет значение, близкое к 0, поскольку данные передаются каждые пятнадцать минут в реальном времени.

David H. 29.05.2024 03:15
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
2
95
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот один из подходов:

df['Corrected'] = (
    df.groupby('Serial', observed=True)['Hours']
    .apply(lambda x: x.diff().where(x.diff().ge(0), x).cumsum())
    .droplevel(0)
    .astype(int)
)

Выход:

   Serial  Hours  Corrected
0   Unit1      3          3
1   Unit1      4          4
2   Unit1      6          6
3   Unit1      2          8
4   Unit1      4         10
5   Unit1      6         12
6   Unit1      1         13
7   Unit1      2         14
8   Unit1      3         15
9   Unit1      4         16
10  Unit1      1         17
11  Unit1      2         18
12  Unit2      5          5
13  Unit2      6          6
14  Unit2      7          7
15  Unit2      8          8
16  Unit2      9          9
17  Unit2     10         10
18  Unit2     11         11
19  Unit2     12         12
20  Unit2     13         13
21  Unit2     14         14

Объяснение

Средний (до подачи заявки cumsum)

(
    df.groupby('Serial', observed=True)['Hours']
    .apply(lambda x: x.diff().where(x.diff().ge(0), x))
)

Serial    
Unit1   0     3.0 # NaN becomes 3 from "Hours"
        1     1.0
        2     2.0
        3     2.0 # -4 becomes 2 from "Hours"
        4     2.0
        5     2.0
        6     1.0 # -5 becomes 1 from "Hours"
        7     1.0
        8     1.0
        9     1.0
        10    1.0 # -3 becomes 1 from "Hours"
        11    1.0
Unit2   12    5.0 # NaN becomes 5 from "Hours"
        13    1.0
        14    1.0
        15    1.0
        16    1.0
        17    1.0
        18    1.0
        19    1.0
        20    1.0
        21    1.0
Name: Hours, dtype: float32

Спасибо за ваше предложение @ouroboros1 — оно отлично сработало! Для моего большого набора данных потребовалось всего 2 секунды. Я тоже очень ценю объяснение.

David H. 29.05.2024 13:50

Так что это работает очень хорошо, но я нашел один сценарий, в котором значения часов для определенного серийного номера выглядели как [113, 113, 112,9, 112,9, 113, ...], а исправленный результат был [113, 113, 225,9, 225,9]. , 226,...]. В этом случае счетчик часов не был сброшен - я полагаю, что на выходе есть некоторый допуск. Нужно подумать, как справиться с этим сценарием.

David H. 29.05.2024 15:31

Другие вопросы по теме