Мне нужно рассчитать разницу между максимальным и минимальным значением за период в 1 секунду, фрейм данных выглядит так, эпоха в миллисекундах.
Мне нужно создать новый столбец diff, который представляет собой разницу между минимальным и максимальным значением 'column A' в каждом интервале в 1 секунду. Обратите внимание, что все эти интервалы относятся к минимальному значению 'Epoch' (первое значение, 1373981385937, в приведенном выше примере). Сначала я получаю первый интервал в 1 секунду, начиная с 1373981385937, добавляю 1 секунду, получаю значения в этом диапазоне, вычисляю максимальную минимальную разницу и устанавливаю diff на это значение для всего диапазона, сохраняя исходный индекс.
Желаемый результат:
Ниже я показываю, как я это делаю сейчас:
current_index = 0
list_indexes = []
list_values = []
interval = 1000 # ms
while current_index < series.shape[0]:
left = series.loc[(series["Epoch"] >= series["Epoch"].iloc[current_index]) & (series["Epoch"] < series["Epoch"].iloc[current_index] + interval)]
value = left["Column A"].max() - left["Column A"].min()
list_indexes.extend(list(left.index.values))
list_values.extend(np.full(left.shape[0], value))
current_index += left.shape[0]
result = pds.Series(data = list_values, index = list_indexes, name = label, dtype=np.float64)
Я получаю ожидаемый результат, но производительность оставляет желать лучшего.
Есть ли способ сделать это быстрее/лучше?
Редактировать:
Спасибо за поддержку, но я не могу интегрировать решение в свой код отчасти потому, что мне нужно учесть еще два столбца.
Как я это делаю сейчас:
for each unique value in colum A
for each unique value in colum B
gb = df.groupby((df["Epoch"] - df["Epoch"].min()) // 1000)["Column C"]
kwargs = {label : gb.transform(max) - gb.transform(min)}
newdf = df.assign(**kwargs)
Извините за долгое редактирование. Вы думаете, что есть лучший способ?
@mozway: я только что понял, что ОП хочет, чтобы 1-секундные интервалы были привязаны к Epoch.min(). @ catalin_345323: я попытался прояснить вопрос и подчеркнуть происхождение 1-секундных интервалов. Пожалуйста, вернитесь, если это не то, что вы имели в виду.
@Pierre хорошо, кажется разумным
Хорошо, я предположил, что вы ищете в отредактированном вопросе, и добавил некоторый материал к моему ответу.
Приведенный ниже код обрабатывает 1 миллион строк примерно за 151 мс (на универсальном процессоре Intel Xeon Platinum 8175M).
Используя ваш пример:
gb = df.groupby((df['Epoch'] - df['Epoch'].min()) // 1000)['Column A']
newdf = df.assign(diff=gb.transform(max) - gb.transform(min))
>>> newdf
Column A Epoch diff
0 10 1373981385937 3
1 11 1373981386140 3
2 13 1373981386312 3
3 8 1373981386968 1
4 7 1373981387187 1
5 7 1373981387421 1
Быстрая проверка: ничего из нижеперечисленного не требуется для решения выше, а просто для того, чтобы убедиться, что результат правильный. Мы назначаем t фактическую дату и время, а delta_t разницу в секундах с t.min():
t = pd.to_datetime(df['Epoch'], unit='ms')
tmp = df.assign(
t=t,
delta_t=(t - t.min()).dt.total_seconds(),
groupno=gb.ngroup(),
)
>>> tmp
Column A Epoch t delta_t groupno
0 10 1373981385937 2013-07-16 13:29:45.937 0.000 0
1 11 1373981386140 2013-07-16 13:29:46.140 0.203 0
2 13 1373981386312 2013-07-16 13:29:46.312 0.375 0
3 8 1373981386968 2013-07-16 13:29:46.968 1.031 1
4 7 1373981387187 2013-07-16 13:29:47.187 1.250 1
5 7 1373981387421 2013-07-16 13:29:47.421 1.484 1
В ответ на комментарий: «Есть ли способ заменить функцию преобразования max/min чем-то, что возвращает первое и последнее значение группы?»
Да:
newdf = df.assign(diff=gb.transform('last') - gb.transform('first'))
>>> newdf
Column A Epoch diff
0 10 1373981385937 3
1 11 1373981386140 3
2 13 1373981386312 3
3 8 1373981386968 -1
4 7 1373981387187 -1
5 7 1373981387421 -1
n = 1_000_000
t0 = 1373981385937
df = pd.DataFrame({
'Column A': np.random.randint(0, 100, n),
'Epoch': np.random.randint(t0, t0 + 300 * n, n),
})
def f(df):
gb = df.groupby((df['Epoch'] - df['Epoch'].min()) // 1000)['Column A']
return df.assign(diff=gb.transform(max) - gb.transform(min))
%timeit f(df)
# 151 ms ± 1.87 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
IIUC, вы хотели бы сделать тот же расчет, но в каждой группе значений [A, B]. Происхождение секундных интервалов времени по-прежнему является глобальным минимумом Epoch.
def f(df):
gb = df.groupby(
['Column A', 'Column B', (df['Epoch'] - df['Epoch'].min()) // 1000]
)['Column C']
return df.assign(diff=gb.transform(max) - gb.transform(min))
Пример предоставленных выборочных данных с двумя дополнительными столбцами:
df = pd.DataFrame({
'Column A': [25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27],
'Column B': [10, 10, 12, 12, 10, 10, 12, 12, 10, 10, 12, 12],
'Column C': [15, 10, 18, 16, 15, 11, 13, 10, 23, 17, 17, 10],
'Epoch': [
1373973055796, 1373973055828, 1373973092296, 1373973092328,
1373973055875, 1373973055906, 1373973092359, 1373973092406,
1373973055953, 1373973056000, 1373973092921, 1373973092953],
})
>>> f(df)
Column A Column B Column C Epoch diff
0 25 10 15 1373973055796 5
1 25 10 10 1373973055828 5
2 25 12 18 1373973092296 2
3 25 12 16 1373973092328 2
4 26 10 15 1373973055875 4
.. ... ... ... ... ...
7 26 12 10 1373973092406 3
8 27 10 23 1373973055953 6
9 27 10 17 1373973056000 6
10 27 12 17 1373973092921 7
11 27 12 10 1373973092953 7
### Speed
n = 1_000_000
t0 = 1373981385937
df = pd.DataFrame({
'Column A': np.random.randint(0, 10, n),
'Column B': np.random.randint(0, 10, n),
'Column C': np.random.randint(0, 10, n),
'Epoch': np.random.randint(t0, t0 + 300 * n, n),
})
%timeit f(df)
# 538 ms ± 4.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Спасибо, мне удается обработать 4 миллиона записей за 700 мс, а в моем исходном коде те же данные заняли чуть меньше 4 минут.
Есть ли способ заменить функцию преобразования max/min чем-то, что возвращает первое и последнее значение группы? Мне это нужно для чего-то похожего, но вместо разницы между max и min мне нужна разница между последним и первым элементом. Я пробовал использовать такую функцию, как def get_last(df): return df.iloc[-1], но она очень медленная.
Да. Смотрите «редактировать» в ответе.
Почему вы получаете 1/1/1 за последние 3? не должны ли они группироваться вместе?