Сдвиг массива numpy
Я хотел бы "срезать" массив numpy. Я не уверен, что правильно использую термин "сдвиг"; под сдвигом я имею в виду что-то вроде:
сдвиньте первый столбец на 0 мест
Сдвинуть второй столбец на 1 место
Сдвиньте третий Колум на 2 места
так далее...
так этот массив:
array([[11, 12, 13],
[17, 18, 19],
[35, 36, 37]])
превратится в либо этот массив:
array([[11, 36, 19],
[17, 12, 37],
[35, 18, 13]])
или что-то вроде этого массива:
array([[11, 0, 0],
[17, 12, 0],
[35, 18, 13]])
в зависимости от того, как мы обрабатываем края. Я не слишком разборчив в поведении.
вот моя попытка функции, которая делает это:
import numpy
def shear(a, strength=1, shift_axis=0, increase_axis=1, edges='clip'):
strength = int(strength)
shift_axis = int(shift_axis)
increase_axis = int(increase_axis)
if shift_axis == increase_axis:
raise UserWarning("Shear can't shift in the direction it increases")
temp = numpy.zeros(a.shape, dtype=int)
indices = []
for d, num in enumerate(a.shape):
coords = numpy.arange(num)
shape = [1] * len(a.shape)
shape[d] = num
coords = coords.reshape(shape) + temp
indices.append(coords)
indices[shift_axis] -= strength * indices[increase_axis]
if edges == 'clip':
indices[shift_axis][indices[shift_axis] < 0] = -1
indices[shift_axis][indices[shift_axis] >= a.shape[shift_axis]] = -1
res = a[indices]
res[indices[shift_axis] == -1] = 0
elif edges == 'roll':
indices[shift_axis] %= a.shape[shift_axis]
res = a[indices]
return res
if __name__ == '__main__':
a = numpy.random.random((3,4))
print a
print shear(a)
Это, кажется, работает. Пожалуйста, скажи мне, если нет!
Он кажется неуклюжим и грубым. Я упускаю встроенную функцию numpy/scipy, которая делает это? Есть ли более чистый / лучший / более эффективный способ сделать это в numpy? Я изобретаю колесо?
EDIT:
Бонусные баллы, если это работает на N-мерном массиве, а не просто в случае 2Д.
эта функция будет в самом центре цикла, который я повторю много раз в нашей обработке данных, поэтому я подозреваю, что на самом деле стоит оптимизировать.
ВТОРОЕ РЕДАКТИРОВАНИЕ: Я наконец-то сделал бенчмаркинг. Похоже, и NumPy.ролл-это путь, несмотря на петлю. Спасибо, tom10 и Свен Марнах!
код бенчмаркинга: (запуск в Windows, не используйте время.часы на Linux, я думаю)
import time, numpy
def shear_1(a, strength=1, shift_axis=0, increase_axis=1, edges='roll'):
strength = int(strength)
shift_axis = int(shift_axis)
increase_axis = int(increase_axis)
if shift_axis == increase_axis:
raise UserWarning("Shear can't shift in the direction it increases")
temp = numpy.zeros(a.shape, dtype=int)
indices = []
for d, num in enumerate(a.shape):
coords = numpy.arange(num)
shape = [1] * len(a.shape)
shape[d] = num
coords = coords.reshape(shape) + temp
indices.append(coords)
indices[shift_axis] -= strength * indices[increase_axis]
if edges == 'clip':
indices[shift_axis][indices[shift_axis] < 0] = -1
indices[shift_axis][indices[shift_axis] >= a.shape[shift_axis]] = -1
res = a[indices]
res[indices[shift_axis] == -1] = 0
elif edges == 'roll':
indices[shift_axis] %= a.shape[shift_axis]
res = a[indices]
return res
def shear_2(a, strength=1, shift_axis=0, increase_axis=1, edges='roll'):
indices = numpy.indices(a.shape)
indices[shift_axis] -= strength * indices[increase_axis]
indices[shift_axis] %= a.shape[shift_axis]
res = a[tuple(indices)]
if edges == 'clip':
res[indices[shift_axis] < 0] = 0
res[indices[shift_axis] >= a.shape[shift_axis]] = 0
return res
def shear_3(a, strength=1, shift_axis=0, increase_axis=1):
if shift_axis > increase_axis:
shift_axis -= 1
res = numpy.empty_like(a)
index = numpy.index_exp[:] * increase_axis
roll = numpy.roll
for i in range(0, a.shape[increase_axis]):
index_i = index + (i,)
res[index_i] = roll(a[index_i], i * strength, shift_axis)
return res
numpy.random.seed(0)
for a in (
numpy.random.random((3, 3, 3, 3)),
numpy.random.random((50, 50, 50, 50)),
numpy.random.random((300, 300, 10, 10)),
):
print 'Array dimensions:', a.shape
for sa, ia in ((0, 1), (1, 0), (2, 3), (0, 3)):
print 'Shift axis:', sa
print 'Increase axis:', ia
ref = shear_1(a, shift_axis=sa, increase_axis=ia)
for shear, label in ((shear_1, '1'), (shear_2, '2'), (shear_3, '3')):
start = time.clock()
b = shear(a, shift_axis=sa, increase_axis=ia)
end = time.clock()
print label + ': %0.6f seconds'%(end-start)
if (b - ref).max() > 1e-9:
print "Something's wrong."
print
5 ответов
подход tom10 это может быть увеличен до произвольных размеров:
def shear3(a, strength=1, shift_axis=0, increase_axis=1):
if shift_axis > increase_axis:
shift_axis -= 1
res = numpy.empty_like(a)
index = numpy.index_exp[:] * increase_axis
roll = numpy.roll
for i in range(0, a.shape[increase_axis]):
index_i = index + (i,)
res[index_i] = roll(a[index_i], -i * strength, shift_axis)
return res
включает в себя свернуть это. Например, если исходный массив равен x, то
for i in range(x.shape[1]):
x[:,i] = np.roll(x[:,i], i)
производит
[[11 36 19]
[17 12 37]
[35 18 13]]
Это можно сделать, используя трюк, описанный в этот ответ Джо Кингтон:
from numpy.lib.stride_tricks import as_strided
a = numpy.array([[11, 12, 13],
[17, 18, 19],
[35, 36, 37]])
shift_axis = 0
increase_axis = 1
b = numpy.vstack((a, a))
strides = list(b.strides)
strides[increase_axis] -= strides[shift_axis]
strides = (b.strides[0], b.strides[1] - b.strides[0])
as_strided(b, shape=b.shape, strides=strides)[a.shape[0]:]
# array([[11, 36, 19],
# [17, 12, 37],
# [35, 18, 13]])
чтобы получить "клип" вместо "roll", используйте
b = numpy.vstack((numpy.zeros(a.shape, int), a))
это, вероятно, самый эффективный способ сделать это, так как он вообще не использует цикл Python.
вот очищенная версия вашего собственного подхода:
def shear2(a, strength=1, shift_axis=0, increase_axis=1, edges='clip'):
indices = numpy.indices(a.shape)
indices[shift_axis] -= strength * indices[increase_axis]
indices[shift_axis] %= a.shape[shift_axis]
res = a[tuple(indices)]
if edges == 'clip':
res[indices[shift_axis] < 0] = 0
res[indices[shift_axis] >= a.shape[shift_axis]] = 0
return res
основное отличие в том, что он использует numpy.indices()
вместо того, чтобы катить свою собственную версию этого.
r = lambda l, n: l[n:]+l[:n]
transpose(map(r, transpose(a), range(0, len(a)))
Я думаю. Вероятно, вы должны рассмотреть этот psuedocode больше, чем фактический Python. В основном транспонируйте массив, сопоставьте общую функцию поворота над ним, чтобы сделать поворот, а затем транспонируйте его обратно.