A generic N-d array solution for an array of arbitrary roll amounts:
def roll_along(arr, shifts, axis, fill_value=None):
assert arr.ndim - 1 == shifts.ndim
axis %= arr.ndim
shape = (1,) * axis + (-1,) + (1,) * (arr.ndim - axis - 1)
dim_indices = np.arange(arr.shape[axis]).reshape(shape)
shifts_ = np.expand_dims(shifts, axis)
if fill_value is not None:
runs_ = -shifts_
left_mask = (runs_ >= 0) & (dim_indices >= runs_)
right_mask = (runs_ < 0) & (dim_indices < runs_ + arr.shape[axis])
mask = left_mask | right_mask
arr = np.where(mask, arr, np.full_like(arr, fill_value))
indices = (dim_indices - shifts_) % arr.shape[axis]
arr = np.take_along_axis(arr, indices, axis)
return arr
Example usage:
>>> arr = np.arange(8 * 7).reshape(8, 7)
>>> arr
array([[ 0, 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12, 13],
[14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27],
[28, 29, 30, 31, 32, 33, 34],
[35, 36, 37, 38, 39, 40, 41],
[42, 43, 44, 45, 46, 47, 48],
[49, 50, 51, 52, 53, 54, 55]])
>>> shifts = np.array([-4, -3, -2, -1, 0, 1, 2, 3])
>>> roll_along(arr, shifts, axis=-1)
array([[ 4, 5, 6, 0, 1, 2, 3],
[10, 11, 12, 13, 7, 8, 9],
[16, 17, 18, 19, 20, 14, 15],
[22, 23, 24, 25, 26, 27, 21],
[28, 29, 30, 31, 32, 33, 34],
[41, 35, 36, 37, 38, 39, 40],
[47, 48, 42, 43, 44, 45, 46],
[53, 54, 55, 49, 50, 51, 52]])
>>> roll_along(arr, shifts, axis=-1, fill_value=0)
array([[ 4, 5, 6, 0, 0, 0, 0],
[10, 11, 12, 13, 0, 0, 0],
[16, 17, 18, 19, 20, 0, 0],
[22, 23, 24, 25, 26, 27, 0],
[28, 29, 30, 31, 32, 33, 34],
[ 0, 35, 36, 37, 38, 39, 40],
[ 0, 0, 42, 43, 44, 45, 46],
[ 0, 0, 0, 49, 50, 51, 52]])