У меня есть фрейм данных, в котором есть столбец t, который представляет собой монотонно увеличивающееся целое число. Я хочу отфильтровать фрейм данных так, чтобы выбрать как можно больше строк, но в ВЫХОДЕ у меня никогда не было двух строк, где t - t.shift(1) <= 10 (и всегда включая первую строку входного кадра данных в выходной). Это отличается от df[df['t'].diff().fillna(20) >= 10], потому что у меня может быть строка входных строк, где t равно 5 друг от друга, и просто взятие разницы на входе приведет к выдаче из них всех.
В неэффективном псевдокоде это будет что-то вроде:
last_chosen = None
inds = []
for i, t in enumerate(df['t']):
if last_chosen is None or t - last_chosen >= 10:
last_chosen = t
inds.append(df.index[i])
new_df = df[inds]
Есть ли какой-нибудь эффективный способ сделать это?
Обновлено: Минимальный пример:
def filt(df, gap=10):
last_chosen = None
inds = []
for i, t in enumerate(df['t']):
if last_chosen is None or t - last_chosen >= gap:
last_chosen = t
inds.append(df.index[i])
return df.loc[inds]
d = pd.DataFrame(dict(
t = [5, 7, 9, 10, 15, 20, 30, 45, 50, 55, 60, 70]))
(d['t'][d['t'].diff().fillna(10) >= 10]) # gives [5, 30, 45, 70]
filt(d)['t'] # gives [5, 15, 30, 45, 55, 70] which is desired
Пример поможет. Особенно тот, который показывает, что вы пробовали, какой результат это дало и чем это отличается от того, что вы хотите. Потому что я не понимаю, почему «строка входных строк, где расстояние между t равно 5», может быть выброшена при использовании diff.
IIUC, вы не можете векторизовать эту операцию. Вы можете использовать numba
, чтобы ускорить процесс.
Похоже, @mozway прав — это похоже на stackoverflow.com/questions/55738163/…, насколько я могу судить, единственным решением был наивный цикл
Как упоминалось ранее в комментариях, вы не можете векторизовать эту операцию. Поскольку наличие значения зависит от судьбы предыдущих значений, необходим итерационный процесс, т. е. цикл.
Таким образом, ваш код в порядке, однако вы можете значительно улучшить его скорость, используя numba. Вам нужно немного изменить код для numba, чтобы он принимал входные данные (в виде массива numpy):
from numba import jit
@jit
def numba_filt(a, gap=10):
last_chosen = None
inds = []
for i, t in enumerate(a):
if last_chosen is None or t - last_chosen >= gap:
last_chosen = t
inds.append(i)
return inds
def filt(df, gap=10, col='t'):
return df.iloc[numba_filt(df[col].to_numpy(), gap=gap)]
filt(d)['t']
Выход:
0 5
4 15
6 30
7 45
9 55
11 70
Name: t, dtype: int64
Протестировав это на 2 млн строк, мы получили увеличение скорости примерно в 5 раз.
# python loop
303 ms ± 6.91 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# numba
60.9 ms ± 682 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Используемый вход:
df = pd.DataFrame({'t': np.sort(np.random.choice(10_000_000, 2_000_000, replace=False))})
import numpy as np
import pandas as pd
df = pd.DataFrame({'t': [5, 7, 9, 10, 15, 20, 30, 45, 50, 55, 60, 70]})
print(df)
gap = 10
t_values = df['t'].values
chosen = np.zeros(len(t_values),dtype= bool)
chosen[0] = True
last_chosen_value= t_values[0]
while True :
next_value = last_chosen_value + gap
next_index = np.searchsorted(t_values,next_value, side ='left')
if next_index >= len(t_values) :break
chosen[next_index] =True
last_chosen_value = t_values[next_index]
print("last_chosen_value :",last_chosen_value)
res = df[chosen]
print(res)
'''
t
0 5
4 15
6 30
7 45
9 55
11 70
'''
Можете ли вы предоставить минимальный воспроизводимый пример (вход + ожидаемый результат) для ясности?