TL; DR в конце
Я бы начал с создания выходного буфера правильного размера, используя np.repeat
, затем заполнив восходящий / нисходящие порции с al oop.
Давайте посмотрим на размер прогонов, которые у вас есть, и разработаем стратегию повторения, чтобы заполнить их. Учитывая набор данных ara
0.51 0.14
0.19 0.44
0.32 0.48
вы хотите получить
0.51 0.14
0.41 0.14 4 = abs(0.19 - 0.51) // step + 1
0.31 0.14
0.21 0.14
---- ----
0.19 0.14
0.19 0.24 3 = abs(0.44 - 0.14) // step + 1
0.19 0.34
---- ----
0.19 0.44 2 = abs(0.32 - 0.19) // step + 1
0.29 0.44
---- ----
0.32 0.44 1 = abs(0.48 - 0.44) // step + 1
---- ----
0.32 0.48 last section is always size 1
Используя информацию о размере, показанную выше, которая явно основана на np.diff(ara, axis=0)
, мы можем сначала создать массив, который выглядит следующим образом:
0.51 0.14
0.51 0.14
0.51 0.14
0.51 0.14
0.19 0.14
0.19 0.14
0.19 0.14
0.19 0.44
0.19 0.44
0.32 0.44
0.32 0.48
Хитрость заключается в том, чтобы повторять все элементы необходимое количество раз:
signs = np.diff(ara, axis=0, append=ara[-1, None]).ravel()[:-1]
d = (np.abs(signs) // step).astype(int) + 1
repeats = np.tile(d, 2)
values = np.repeat(ara.ravel(order='F'), 2)[1:-1]
buffer = np.repeat(values, repeats).reshape(-1, 2, order='F')
Оставшаяся часть - заполнять диапазоны возрастающих / убывающих чисел. Это легко сделать с помощью for
l oop:
ends = np.cumsum(d)
starts = np.zeros_like(end)
starts[1:] = ends[:-1]
for col, start, end in zip(itertools.cycle((0, 1)), starts, ends):
s = buffer[start, col]
e = buffer[end, col]
buffer[start:end, col] = np.arange(s, e, np.copysign(step, e - s))
Но это "неинтересно", потому что используется for
l oop. Итак, давайте сделаем действительно векторизованное решение. Для начала нам нужен массив кумулятивных сумм, который мы можем использовать для добавления в каждый восходящий / нисходящий раздел. Если мы просто сделаем np.arange(buffer.shape[0]) * step
, сбросим на каждой границе раздела и получим правильный знак, мы можем просто добавить это в буфер, чтобы получить вывод. Итак, представьте следующие операции:
( 0 - 0) * step * sign(0.19 - 0.51)
( 1 - 0) * step * sign(0.19 - 0.51)
( 2 - 0) * step * sign(0.19 - 0.51)
( 3 - 0) * step * sign(0.19 - 0.51)
-- --
( 4 - 4) * step * sign(0.44 - 0.14)
( 5 - 4) * step * sign(0.44 - 0.14)
( 6 - 4) * step * sign(0.44 - 0.14)
-- --
( 7 - 7) * step * sign(0.32 - 0.19)
( 8 - 7) * step * sign(0.32 - 0.19)
-- --
( 9 - 9) * step * sign(0.48 - 0.44)
-- --
(10 - 10) * step * "Doesn't matter"
Первый столбец - это увеличивающийся диапазон. Второй столбец - это смещение для каждого сечения, которое выглядит как совокупная сумма длин сечения. Знаки - это уже то, что мы вычислили.
Вся операция выглядит следующим образом:
numbers = np.arange(buffer.shape[0])
offsets = np.zeros(d.size)
offsets[1:] = np.cumsum(d[:-1])
offsets = np.repeat(offsets, d)
signs = np.repeat(signs, d)
ramps = (numbers - offsets) * np.copysign(step, signs)
Перед добавлением этого в выходной буфер, мы должны разделить этот массив на два столбца, чередуя по разделу. Вы можете сделать это, дублируя ramps
на два столбца и устанавливая ненужные элементы в ноль:
ramps = np.stack((ramps, ramps), axis=1)
mask = np.zeros((d.size, 2))
mask[::2, 0] = mask[1::2, 1] = 1
mask = np.repeat(mask, d, axis=0)
buffer += ramps * mask
TL; DR
Вот полностью векторизованный решение:
def twistpop_func(ara):
signs = np.diff(ara, axis=0, append=ara[-1, None]).ravel()[:-1]
d = (np.abs(signs) // step).astype(int) + 1
repeats = np.tile(d, 2)
values = np.repeat(ara.ravel(order='F'), 2)[1:-1]
buffer = np.repeat(values, repeats).reshape(-1, 2, order='F')
numbers = np.arange(buffer.shape[0])
offsets = np.zeros(d.size)
offsets[1:] = np.cumsum(d[:-1])
offsets = np.repeat(offsets, d)
signs = np.repeat(signs, d)
ramps = (numbers - offsets) * np.copysign(step, signs)
ramps = np.stack((ramps, ramps), axis=1)
mask = np.zeros((d.size, 2))
mask[::2, 0] = mask[1::2, 1] = 1
mask = np.repeat(mask, d, axis=0)
buffer += ramps * mask
return buffer