Основная идея
Как обычно панды будут тратить время на поиск этого конкретного индекса в data_series.loc[s:e]
, где s
и e
- индексы даты и времени. Это дорого обходится в цикле, и это именно то, что мы могли бы улучшить Мы нашли бы все эти индексы в векторизованном виде с searchsorted
Затем мы извлекаем значения из data_series
в виде массива и используем те индексы, полученные из searchsorted
, с простой целочисленной индексацией. Таким образом, был бы цикл с минимальной работой простого вырезания массива.
Общая мантра - Большинство работает с предварительной обработкой в векторизованном виде и минимально при зацикливании.
Реализация будет выглядеть примерно так -
def select_slices_by_index(data_series, start, end):
idx = data_series.index.values
S = np.searchsorted(idx,start.values)
E = np.searchsorted(idx,end.values)
ar = data_series.values
return [ar[i:j] for (i,j) in zip(S,E+1)]
Использование NumPy-striding
Для конкретного случая, когда период времени между starts
и ends
одинаков для всех записей, и все срезы охватываются этой длиной, то есть нет случаев за пределами допустимого диапазона, мы можем использовать NumPy's sliding window trick
.
Мы можем использовать np.lib.stride_tricks.as_strided
на основе scikit-image's view_as_windows
, чтобы получить раздвижные окна. Подробнее об использовании as_strided
на основе view_as_windows
.
from skimage.util.shape import view_as_windows
def select_slices_by_index_strided(data_series, start, end):
idx = data_series.index.values
L = np.searchsorted(idx,end.values[0])-np.searchsorted(idx,start.values[0])+1
S = np.searchsorted(idx,start.values)
ar = data_series.values
w = view_as_windows(ar,L)
return w[S]
Используйте this post
, если у вас нет доступа к scikit-image
.
Бенчмаркинг
Давайте увеличим все на 100x
на данных образца и протестируем.
Настройка -
np.random.seed(0)
start = pd.Series(pd.date_range('20190412',freq='H',periods=2500))
# Drop a few indexes to make the series not sequential
start.drop([4,5,10,14]).reset_index(drop=True,inplace=True)
# Add some random minutes to the start as it's not necessarily quantized
start = start + pd.to_timedelta(np.random.randint(59,size=len(start)),unit='T')
end = start + pd.Timedelta('5H')
data_series = pd.Series(data=np.random.randint(20, size=(750*600)),
index=pd.date_range('20190411',freq='T',periods=(750*600)))
Сроки -
In [156]: %%timeit
...: frm = []
...: for s,e in zip(start,end):
...: frm.append(data_series.loc[s:e].values)
1 loop, best of 3: 172 ms per loop
In [157]: %timeit select_slices_by_index(data_series, start, end)
1000 loops, best of 3: 1.23 ms per loop
In [158]: %timeit select_slices_by_index_strided(data_series, start, end)
1000 loops, best of 3: 994 µs per loop
In [161]: frm = []
...: for s,e in zip(start,end):
...: frm.append(data_series.loc[s:e].values)
In [162]: np.allclose(select_slices_by_index(data_series, start, end),frm)
Out[162]: True
In [163]: np.allclose(select_slices_by_index_strided(data_series, start, end),frm)
Out[163]: True
140x+
и 170x
ускорений с этими!