np.roll¶
The np.roll() function shifts array elements along an axis with circular wrapping.
Mental Model
np.roll shifts every element by a fixed offset and wraps elements that fall off one end back to the other, like a circular conveyor belt. Positive shifts move elements to the right (or down), negative shifts move left (or up). No data is lost -- the array is just rotated in place along the chosen axis.
Why Roll Exists
Circular shifting arises naturally in domains with periodic boundary conditions: FFT frequency bins wrap around, time-series data has seasonal cycles, and image processing uses circular convolution. np.roll provides this operation directly on arrays without manual index arithmetic. It fits into the manipulation system as the shift operation — alongside combine (concatenate), divide (split), and reorder (transpose).
Basic Concept¶
1D Array Rolling¶
```python import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(np.roll(arr, 2)) # [4, 5, 1, 2, 3] - shift right by 2 print(np.roll(arr, -2)) # [3, 4, 5, 1, 2] - shift left by 2 ```
Image Rolling¶
Basic Example¶
```python import matplotlib.pyplot as plt import numpy as np import PIL import urllib
def main(): url = "https://upload.wikimedia.org/wikipedia/en/4/43/Pok%C3%A9mon_Mewtwo_art.png" img = np.array(PIL.Image.open(urllib.request.urlopen(url)))
fig, (ax0, ax1, ax2) = plt.subplots(1, 3, figsize=(12, 3))
ax0.imshow(img)
ax1.imshow(np.roll(img, 50, axis=0)) # Roll along rows (vertical)
ax2.imshow(np.roll(img, 50, axis=1)) # Roll along columns (horizontal)
for ax in (ax0, ax1, ax2):
ax.axis('off')
plt.tight_layout()
plt.show()
if name == "main": main() ```
Axis Reference¶
Roll Directions¶
| Axis | Direction | Effect |
|---|---|---|
| 0 | Vertical | Rows wrap top-to-bottom |
| 1 | Horizontal | Columns wrap left-to-right |
Positive vs Negative Shift¶
```python import matplotlib.pyplot as plt import numpy as np import PIL import urllib
url = "https://upload.wikimedia.org/wikipedia/en/4/43/Pok%C3%A9mon_Mewtwo_art.png" img = np.array(PIL.Image.open(urllib.request.urlopen(url)))
fig, axes = plt.subplots(2, 3, figsize=(12, 8))
Row 1: Horizontal rolling (axis=1)¶
axes[0, 0].imshow(np.roll(img, -100, axis=1)) axes[0, 0].set_title('roll(img, -100, axis=1)')
axes[0, 1].imshow(img) axes[0, 1].set_title('Original')
axes[0, 2].imshow(np.roll(img, 100, axis=1)) axes[0, 2].set_title('roll(img, 100, axis=1)')
Row 2: Vertical rolling (axis=0)¶
axes[1, 0].imshow(np.roll(img, -100, axis=0)) axes[1, 0].set_title('roll(img, -100, axis=0)')
axes[1, 1].imshow(img) axes[1, 1].set_title('Original')
axes[1, 2].imshow(np.roll(img, 100, axis=0)) axes[1, 2].set_title('roll(img, 100, axis=0)')
for ax in axes.flat: ax.axis('off') plt.tight_layout() plt.show() ```
Multi-Axis Rolling¶
Roll Both Axes¶
```python import matplotlib.pyplot as plt import numpy as np
Roll in both directions¶
rolled_both = np.roll(np.roll(img, 50, axis=0), 75, axis=1)
Or use tuple for multi-axis roll¶
rolled_tuple = np.roll(img, (50, 75), axis=(0, 1))
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
axes[0].imshow(img) axes[0].set_title('Original')
axes[1].imshow(rolled_both) axes[1].set_title('Sequential Roll')
axes[2].imshow(rolled_tuple) axes[2].set_title('Tuple Roll (50, 75)')
for ax in axes: ax.axis('off') plt.tight_layout() plt.show() ```
Animation Effect¶
Creating Rolling Animation Frames¶
```python import matplotlib.pyplot as plt import numpy as np
def create_roll_frames(img, n_frames=10, axis=1): """Create frames for rolling animation.""" step = img.shape[axis] // n_frames frames = [] for i in range(n_frames): frames.append(np.roll(img, i * step, axis=axis)) return frames
frames = create_roll_frames(img, n_frames=5, axis=1)
fig, axes = plt.subplots(1, 5, figsize=(15, 3)) for ax, frame in zip(axes, frames): ax.imshow(frame) ax.axis('off') plt.tight_layout() plt.show() ```
Where Rolling Appears
Circular shifts are not just an image trick. They show up in periodic boundary conditions (physics simulations), signal processing (aligning waveforms), and FFT work (centering the zero-frequency component with np.fft.fftshift, which is roll under the hood).
Practical Applications¶
Seamless Texture Check¶
```python import matplotlib.pyplot as plt import numpy as np
def check_seamless(img, shift_fraction=0.5): """Check if texture tiles seamlessly.""" h, w = img.shape[:2]
rolled_h = np.roll(img, int(h * shift_fraction), axis=0)
rolled_w = np.roll(img, int(w * shift_fraction), axis=1)
rolled_both = np.roll(np.roll(img, int(h * shift_fraction), axis=0),
int(w * shift_fraction), axis=1)
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
axes[0, 0].imshow(img)
axes[0, 0].set_title('Original')
axes[0, 1].imshow(rolled_w)
axes[0, 1].set_title('Horizontal Roll')
axes[1, 0].imshow(rolled_h)
axes[1, 0].set_title('Vertical Roll')
axes[1, 1].imshow(rolled_both)
axes[1, 1].set_title('Both Axes Roll')
for ax in axes.flat:
ax.axis('off')
plt.tight_layout()
plt.show()
check_seamless(img) ```
Centering Object¶
```python import matplotlib.pyplot as plt import numpy as np
def center_object(img, current_center, target_center=None): """Roll image to center an object.""" h, w = img.shape[:2] if target_center is None: target_center = (h // 2, w // 2)
shift_y = target_center[0] - current_center[0]
shift_x = target_center[1] - current_center[1]
return np.roll(np.roll(img, shift_y, axis=0), shift_x, axis=1)
Example: move object from (100, 150) to center¶
centered = center_object(img, current_center=(100, 150))
fig, axes = plt.subplots(1, 2, figsize=(10, 5)) axes[0].imshow(img) axes[0].set_title('Original') axes[1].imshow(centered) axes[1].set_title('Centered')
for ax in axes: ax.axis('off') plt.tight_layout() plt.show() ```
Panorama Creation¶
```python import matplotlib.pyplot as plt import numpy as np
def create_panorama_effect(img, repetitions=3): """Create a panorama by tiling and rolling.""" # Tile horizontally tiled = np.tile(img, (1, repetitions, 1))
# Show different roll positions
fig, axes = plt.subplots(3, 1, figsize=(15, 9))
for i, ax in enumerate(axes):
shift = i * img.shape[1] // 2
rolled = np.roll(tiled, shift, axis=1)
# Crop to original width
cropped = rolled[:, :img.shape[1]*2]
ax.imshow(cropped)
ax.set_title(f'Pan position {i+1}')
ax.axis('off')
plt.tight_layout()
plt.show()
create_panorama_effect(img) ```
Comparison with Other Operations¶
Roll vs Slice¶
```python import matplotlib.pyplot as plt import numpy as np
shift = 100
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
Original¶
axes[0].imshow(img) axes[0].set_title('Original')
Roll (circular - wraps around)¶
axes[1].imshow(np.roll(img, shift, axis=1)) axes[1].set_title('Roll (circular)')
Slice (non-circular - loses data)¶
sliced = np.concatenate([img[:, shift:], np.zeros_like(img[:, :shift])], axis=1) axes[2].imshow(sliced.astype(np.uint8)) axes[2].set_title('Slice (loses data)')
for ax in axes: ax.axis('off') plt.tight_layout() plt.show() ```
Grid of Roll Amounts¶
Various Shift Values¶
```python import matplotlib.pyplot as plt import numpy as np
shifts = [0, 50, 100, 150, 200]
fig, axes = plt.subplots(2, 5, figsize=(15, 6))
Horizontal rolls¶
for ax, shift in zip(axes[0], shifts): ax.imshow(np.roll(img, shift, axis=1)) ax.set_title(f'axis=1, shift={shift}') ax.axis('off')
Vertical rolls¶
for ax, shift in zip(axes[1], shifts): ax.imshow(np.roll(img, shift, axis=0)) ax.set_title(f'axis=0, shift={shift}') ax.axis('off')
plt.tight_layout() plt.show() ```
Exercises¶
Exercise 1.
Create a = np.arange(10). Roll it by 3 positions and by -2 positions. Verify that rolling by n then by -n returns the original array.
Solution to Exercise 1
import numpy as np
a = np.arange(10)
rolled = np.roll(a, 3)
unrolled = np.roll(rolled, -3)
print(f"Original: {a}")
print(f"Rolled +3: {rolled}")
print(f"Rolled back: {unrolled}")
print(f"Match: {np.array_equal(a, unrolled)}")
Exercise 2. Create a 4x4 matrix and roll it by 1 along axis 0 (shift rows down) and by -1 along axis 1 (shift columns left). Print the results.
Solution to Exercise 2
import numpy as np
M = np.arange(16).reshape(4, 4)
print(f"Original:\n{M}")
print(f"Roll rows +1:\n{np.roll(M, 1, axis=0)}")
print(f"Roll cols -1:\n{np.roll(M, -1, axis=1)}")
Exercise 3.
Implement a circular convolution of two 1D arrays using np.roll: given a = np.array([1, 2, 3, 0, 0]) and b = np.array([1, 1, 0, 0, 0]), compute the circular convolution by summing a * np.roll(b, k) for each shift k. Compare with np.fft.ifft(np.fft.fft(a) * np.fft.fft(b)).real.
Solution to Exercise 3
import numpy as np
a = np.array([1, 2, 3, 0, 0], dtype=float)
b = np.array([1, 1, 0, 0, 0], dtype=float)
# Circular convolution via roll
result = np.zeros(len(a))
for k in range(len(a)):
result += a[k] * np.roll(b, k)
# FFT-based circular convolution
result_fft = np.fft.ifft(np.fft.fft(a) * np.fft.fft(b)).real
print(f"Roll method: {result}")
print(f"FFT method: {result_fft}")
print(f"Match: {np.allclose(result, result_fft)}")