Skip to content

Reshaping Arrays

NumPy makes it cheap to re-view the same data with different shapes — without copying memory.

.reshape() — change the shape

The total number of elements must stay the same.

import numpy as np

a = np.arange(12)
print("Original:", a, "shape:", a.shape)

# 3 rows × 4 cols
b = a.reshape(3, 4)
print(b)
print("shape:", b.shape)

# 2 layers × 2 rows × 3 cols (3D)
c = a.reshape(2, 2, 3)
print(c)
print("shape:", c.shape)

-1 — "figure it out"

Let NumPy compute one dimension from the rest:

import numpy as np

a = np.arange(12)

print(a.reshape(3, -1).shape)      # (3, 4)  — second dim inferred
print(a.reshape(-1, 4).shape)      # (3, 4)  — first dim inferred
print(a.reshape(2, -1, 3).shape)   # (2, 2, 3)

You can only use -1 once.

.flatten() and .ravel() — 1D again

Both turn any shape into a 1D array. Difference:

  • .flatten() returns a copy.
  • .ravel() returns a view when possible (faster, but changes propagate).
import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6]])

print(a.flatten())      # copy
print(a.ravel())        # view

For most cases, use ravel. Use flatten when you'll modify the result and want the original safe.

.T — transpose

Swap axes.

import numpy as np

a = np.array([
    [1, 2, 3],
    [4, 5, 6],
])
print("Original:")
print(a)
print("shape:", a.shape)

print("\nTransposed:")
print(a.T)
print("shape:", a.T.shape)

.T for 1D does nothing — there's only one axis. To "transpose" a 1D vector into a column:

import numpy as np

a = np.array([1, 2, 3])
print("a       :", a.shape)             # (3,)
print("a[:,None]:", a[:, None].shape)   # (3, 1)
print("a[None,:]:", a[None, :].shape)   # (1, 3)

np.transpose() and .swapaxes()

For arrays with > 2 dimensions:

import numpy as np

a = np.zeros((2, 3, 4))         # shape (2, 3, 4)

# Reverse all axes
print(a.T.shape)                 # (4, 3, 2)

# Custom — make axis 2 the first, axis 0 the second, axis 1 the third
print(np.transpose(a, (2, 0, 1)).shape)   # (4, 2, 3)

# Just swap two axes
print(a.swapaxes(0, 2).shape)             # (4, 3, 2)

Used a lot in deep learning: PyTorch tensors are (batch, channels, h, w), image libraries use (batch, h, w, channels). transpose converts between them.

Adding / removing dimensions

import numpy as np

a = np.array([1, 2, 3])
print(a.shape)                    # (3,)

# Add a dimension at the front
b = a[np.newaxis, :]
print(b.shape, b)                 # (1, 3)

# Add a dimension at the end
c = a[:, np.newaxis]
print(c.shape, c)                 # (3, 1)

# np.expand_dims is the explicit version
d = np.expand_dims(a, axis=0)
print(d.shape)                    # (1, 3)

# Remove dimensions of size 1
e = np.zeros((1, 3, 1, 5))
print(e.shape, "→", np.squeeze(e).shape)

Reshaping rules — what's actually happening

NumPy stores arrays as a flat buffer in memory. Reshape just gives the same buffer a different "interpretation":

[1, 2, 3, 4, 5, 6]    (flat buffer of length 6)

reshape(2, 3):         reshape(3, 2):
[[1, 2, 3],           [[1, 2],
 [4, 5, 6]]            [3, 4],
                       [5, 6]]

Order: row-by-row (C-order, the default). To use column-major (Fortran-order):

import numpy as np

a = np.arange(6)

print(a.reshape(2, 3, order="C"))      # row-major (default)
print(a.reshape(2, 3, order="F"))      # column-major

A common pattern — preparing data for ML

import numpy as np

# Imagine 100 grayscale 8x8 images
images = np.random.default_rng(0).random((100, 8, 8))
print("images shape:", images.shape)

# Flatten each image to a feature vector
flat = images.reshape(100, -1)
print("flat shape:  ", flat.shape)       # (100, 64) — 64 features per image

This is exactly what sklearn expects: rows = samples, columns = features.

Mini-exercise — reverse a 2D image vertically

import numpy as np

img = np.array([
    [1, 1, 1, 1],
    [2, 2, 2, 2],
    [3, 3, 3, 3],
    [4, 4, 4, 4],
])

# Flip vertically
print(np.flip(img, axis=0))

# Flip horizontally
print(np.flip(img, axis=1))

# 90-degree rotation
print(np.rot90(img))

Reshape quick-reference

Goal Method
Change shape arr.reshape(new_shape)
Let NumPy infer one dim arr.reshape(2, -1)
Flatten (copy) arr.flatten()
Flatten (view, fast) arr.ravel()
Swap rows and cols arr.T
Custom axis swap np.transpose(arr, (2,0,1))
Add a dimension arr[None, :] or np.expand_dims(arr, axis=0)
Remove size-1 dims np.squeeze(arr)
Flip an axis np.flip(arr, axis=...)
Rotate 90° np.rot90(arr)

Common pitfalls

  • Wrong number of elementsreshape raises ValueError if the new shape doesn't fit. 12 elements can be reshaped to (3, 4) but not (4, 4).
  • Reshape returns a view, not a copy — modifying the result modifies the original. Use .copy() if you need independence.
  • -1 only once — you can't reshape to (-1, -1). Only one dimension can be inferred.
  • .T does nothing on 1D — surprising. Use [:, None] to make a column.
  • Order matters for non-contiguous arrays — sometimes reshape can't return a view and silently makes a copy. For perf-critical code, check arr.flags.c_contiguous.

Practice

What does this print?

Expected: (2, 3)

import numpy as np
a = np.arange(6).reshape(2, -1)
print(a.shape)

Make a and b independent (mutating b shouldn't affect a)

Expected: [1 2 3 4 5 6]

import numpy as np
a = np.array([1, 2, 3, 4, 5, 6])
b = a.reshape(2, 3)        # bug: reshape returns a VIEW; modifying b changes a
b[0, 0] = 999
print(a)

Quiz — Quick check

What you remember

Q1. What does -1 mean in arr.reshape(2, -1)?

  • Reverse the array
  • Drop the last element
  • "Figure out this dimension automatically from the size"
  • Use the default shape

Why: -1 is a placeholder NumPy fills in based on the total element count. arr.reshape(2, -1) for 12 elements gives shape (2, 6).

Q2. Difference between .flatten() and .ravel()?

  • No difference
  • .flatten() returns a copy; .ravel() returns a view when possible
  • .flatten() is faster
  • .ravel() is deprecated

Why: .ravel() is more memory-efficient (view) but modifying its result can affect the original. Use .flatten() when you need independence.

Q3. How do you convert a 1D array of shape (5,) into a column vector (5, 1)?

  • a.T
  • a.reshape(5)
  • a[:, None] (or a.reshape(-1, 1))
  • a.transpose()

Why: .T does nothing on 1D arrays. To add a column dimension, use a[:, None] (which inserts a new axis of size 1) or a.reshape(-1, 1).

Common doubts

Does reshape always return a view?

Usually yes — when the new shape is compatible with the underlying memory layout. For non-contiguous arrays (e.g. after slicing oddly), reshape may silently return a copy. Check arr.flags.owndata or result.base is arr if it matters.

When should I use np.transpose(a, axes) vs a.T?

For 2D, .T is the shortcut. For 3D+ where you need to swap specific axes (e.g. PyTorch's (N, C, H, W) vs (N, H, W, C) image layout), use np.transpose(a, (0, 2, 3, 1)) to specify the new order explicitly.

Why do I get -1 errors like 'cannot reshape array of size 10 into shape (3, -1)'?

Because 10 doesn't divide evenly by 3. The -1 slot still has to be an integer. Pick a first dimension that divides the total size (e.g. 5 or 2).

What's next

Math Operations & Universal Functions