Я анализирую данные счетчиков часов работы набора машин. Когда машина включена, счетчик часов увеличивается, в противном случае он остается прежним. Для всех машин я должен ожидать только увеличения показателей счетчика моточасов. Однако бывают случаи, когда обновление программного обеспечения (или другая причина, не зависящая от меня) приводит к сбросу счетчика часов в любой момент времени и возобновлению передачи данных счетчика часов с 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, чтобы найти индексные числа, в которых происходит отрицательное изменение часов, а затем пересчитывает, какими должны быть «Часы».
Я не пробовал это на своем реальном наборе данных, но хотел посмотреть, есть ли более элегантное решение проблемы.
@PandaKim согласился. Однако частота отчетов такова, что после отдыха будет значение, близкое к 0, поскольку данные передаются каждые пятнадцать минут в реальном времени.






Вот один из подходов:
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
Объяснение
>= 0 (с Series.ge), иначе мы берем значение из «Часов» (это будет начальное значение для каждой группы (a NaN) и в противном случае все сбрасывается ценности).groupby, чтобы избавиться от добавленного уровня индекса, и добавляем series как новый столбец «Исправлено». При желании измените числа с плавающей запятой на целые числа с помощью Series.astype.Средний (до подачи заявки 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 секунды. Я тоже очень ценю объяснение.
Так что это работает очень хорошо, но я нашел один сценарий, в котором значения часов для определенного серийного номера выглядели как [113, 113, 112,9, 112,9, 113, ...], а исправленный результат был [113, 113, 225,9, 225,9]. , 226,...]. В этом случае счетчик часов не был сброшен - я полагаю, что на выходе есть некоторый допуск. Нужно подумать, как справиться с этим сценарием.
По моему мнению, должна быть четкая логика, чтобы понять, что машина была перезагружена. В
3->4->6->26 -> 2явно сбросился, но у нас нет оснований полагать, что3 -> 4тоже не сбросился.