How to Transpose an Array in NumPy

Array transposition—swapping rows and columns—is one of the most common operations in numerical computing. Whether you're preparing matrices for multiplication, reshaping data for machine learning...

Key Insights

  • NumPy offers three ways to transpose arrays: the .T attribute for quick 2D operations, numpy.transpose() for flexible axis reordering, and the .transpose() method for object-oriented code
  • Transposition returns a view of the original array, not a copy—this is memory-efficient but means modifications affect the original data
  • For higher-dimensional arrays, understanding axis ordering is critical; the axes parameter lets you specify exactly how dimensions should be rearranged

Array transposition—swapping rows and columns—is one of the most common operations in numerical computing. Whether you’re preparing matrices for multiplication, reshaping data for machine learning models, or converting image formats between frameworks, you’ll reach for transposition constantly. NumPy provides multiple ways to accomplish this, each with its own strengths. This guide covers all of them with practical examples you can use immediately.

Understanding Array Dimensions and Axes

Before transposing anything, you need to understand how NumPy thinks about array dimensions. NumPy uses zero-indexed axes: axis 0 is the first dimension (rows in 2D), axis 1 is the second (columns in 2D), and so on for higher dimensions.

Transposition fundamentally reorders these axes. For a 2D array, it swaps axis 0 and axis 1. For higher-dimensional arrays, the default behavior reverses all axes.

import numpy as np

# 1D array - shape (4,)
arr_1d = np.array([1, 2, 3, 4])
print(f"1D shape: {arr_1d.shape}")  # (4,)

# 2D array - shape (2, 3) means 2 rows, 3 columns
arr_2d = np.array([[1, 2, 3],
                   [4, 5, 6]])
print(f"2D shape: {arr_2d.shape}")  # (2, 3)

# 3D array - shape (2, 3, 4) means 2 blocks, 3 rows, 4 columns
arr_3d = np.random.randint(0, 10, size=(2, 3, 4))
print(f"3D shape: {arr_3d.shape}")  # (2, 3, 4)

When you transpose the 2D array above, the shape changes from (2, 3) to (3, 2). The element at position [0, 2] (value 3) moves to position [2, 0]. For the 3D array, a default transpose reverses all axes, changing (2, 3, 4) to (4, 3, 2).

The .T Attribute

The .T attribute is the fastest way to transpose a 2D array. It’s concise, readable, and does exactly what you expect.

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

print("Original:")
print(matrix)
print(f"Shape: {matrix.shape}")

print("\nTransposed:")
print(matrix.T)
print(f"Shape: {matrix.T.shape}")

Output:

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

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

However, .T has a significant limitation with 1D arrays: it does nothing.

vector = np.array([1, 2, 3, 4])
print(f"Original: {vector}, shape: {vector.shape}")
print(f"Transposed: {vector.T}, shape: {vector.T.shape}")

Output:

Original: [1 2 3 4], shape: (4,)
Transposed: [1 2 3 4], shape: (4,)

A 1D array has only one axis, so there’s nothing to swap. If you need a column vector, reshape explicitly:

# Convert to column vector
column = vector.reshape(-1, 1)  # Shape: (4, 1)
# Or use np.newaxis
column = vector[:, np.newaxis]  # Shape: (4, 1)

Using numpy.transpose()

The numpy.transpose() function provides more control through its optional axes parameter. Without arguments, it behaves identically to .T.

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

# These produce identical results
result1 = np.transpose(matrix)
result2 = matrix.T

print(np.array_equal(result1, result2))  # True

The real power emerges with higher-dimensional arrays. The axes parameter lets you specify exactly how to reorder dimensions:

# 3D array: shape (2, 3, 4)
# Think of it as 2 matrices, each with 3 rows and 4 columns
arr_3d = np.arange(24).reshape(2, 3, 4)
print(f"Original shape: {arr_3d.shape}")

# Default transpose reverses axes: (2,3,4) -> (4,3,2)
default_transpose = np.transpose(arr_3d)
print(f"Default transpose shape: {default_transpose.shape}")

# Custom axis order: move axis 2 to position 0, keep 1, move 0 to position 2
# (2,3,4) -> (4,3,2) but specified explicitly
custom_transpose = np.transpose(arr_3d, axes=(2, 1, 0))
print(f"Custom transpose shape: {custom_transpose.shape}")

# Different reordering: (2,3,4) -> (3,4,2)
another_transpose = np.transpose(arr_3d, axes=(1, 2, 0))
print(f"Another transpose shape: {another_transpose.shape}")

The axes tuple specifies where each original axis ends up. With axes=(2, 1, 0), the original axis 2 becomes the new axis 0, axis 1 stays at position 1, and axis 0 moves to position 2.

The .transpose() Method

NumPy arrays also have a .transpose() method that mirrors the function’s behavior. Use whichever fits your coding style.

arr = np.arange(24).reshape(2, 3, 4)

# Function approach
result_func = np.transpose(arr, axes=(1, 2, 0))

# Method approach - axes can be passed as tuple or unpacked
result_method1 = arr.transpose((1, 2, 0))
result_method2 = arr.transpose(1, 2, 0)  # Unpacked version

# All three are equivalent
print(np.array_equal(result_func, result_method1))  # True
print(np.array_equal(result_func, result_method2))  # True

I prefer the function for standalone operations and the method when chaining operations:

# Method chaining reads naturally
processed = arr.transpose(1, 2, 0).reshape(-1, 2).astype(np.float32)

Transposing for Specific Use Cases

Matrix Multiplication Preparation

Matrix multiplication requires compatible dimensions: the columns of the first matrix must equal the rows of the second. Transposition often makes incompatible matrices compatible.

# Two matrices that can't be multiplied directly
A = np.array([[1, 2, 3],
              [4, 5, 6]])  # Shape: (2, 3)

B = np.array([[1, 2],
              [3, 4]])     # Shape: (2, 2)

# A @ B fails: (2,3) @ (2,2) - inner dimensions don't match
# But A @ B.T works if we want (2,3) @ (2,2).T = (2,3) @ (2,2) - still fails
# Let's try A.T @ B: (3,2) @ (2,2) = (3,2) - works!

result = A.T @ B
print(f"A.T shape: {A.T.shape}")
print(f"B shape: {B.shape}")
print(f"Result shape: {result.shape}")
print(result)

Image Channel Reordering

Deep learning frameworks use different image format conventions. OpenCV and TensorFlow use HWC (Height, Width, Channels), while PyTorch uses CHW (Channels, Height, Width). Transposition handles the conversion.

# Simulated RGB image: 224x224 pixels, 3 channels (HWC format)
image_hwc = np.random.randint(0, 256, size=(224, 224, 3), dtype=np.uint8)
print(f"HWC format: {image_hwc.shape}")  # (224, 224, 3)

# Convert to CHW for PyTorch
image_chw = np.transpose(image_hwc, axes=(2, 0, 1))
print(f"CHW format: {image_chw.shape}")  # (3, 224, 224)

# Convert back to HWC
image_hwc_restored = np.transpose(image_chw, axes=(1, 2, 0))
print(f"Restored HWC: {image_hwc_restored.shape}")  # (224, 224, 3)

# Batch of images: add batch dimension
batch_nhwc = np.random.randint(0, 256, size=(32, 224, 224, 3), dtype=np.uint8)
batch_nchw = np.transpose(batch_nhwc, axes=(0, 3, 1, 2))
print(f"Batch NHWC: {batch_nhwc.shape}")  # (32, 224, 224, 3)
print(f"Batch NCHW: {batch_nchw.shape}")  # (32, 3, 224, 224)

Broadcasting Alignment

Sometimes you need to transpose arrays to align dimensions for broadcasting:

# Row-wise operation
data = np.array([[1, 2, 3],
                 [4, 5, 6],
                 [7, 8, 9]])

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

# Multiply each row by corresponding weight
# Need weights as column vector for broadcasting
weighted = data * row_weights.reshape(-1, 1)
print(weighted)

Performance Considerations and Views vs Copies

Transposition in NumPy returns a view, not a copy. This is crucial for both performance and correctness.

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

transposed = original.T

# Check if they share memory
print(f"Shares memory: {np.shares_memory(original, transposed)}")  # True

# Modifying the transposed array affects the original
transposed[0, 0] = 999
print("Original after modifying transposed:")
print(original)  # First element is now 999

Output:

Shares memory: True
Original after modifying transposed:
[[999   2   3]
 [  4   5   6]]

Views are memory-efficient—no data is copied. But if you need an independent array, use .copy():

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

# Create an independent copy
transposed_copy = original.T.copy()

print(f"Shares memory: {np.shares_memory(original, transposed_copy)}")  # False

transposed_copy[0, 0] = 999
print("Original after modifying copy:")
print(original)  # Unchanged

One gotcha: transposed views may have non-contiguous memory layouts, which can slow down subsequent operations. If performance matters and you’re doing many operations on the transposed array, copying might actually be faster:

# Check memory layout
arr = np.arange(12).reshape(3, 4)
print(f"Original C-contiguous: {arr.flags['C_CONTIGUOUS']}")  # True
print(f"Transposed C-contiguous: {arr.T.flags['C_CONTIGUOUS']}")  # False

# For heavy computation, a contiguous copy may be faster
transposed_contiguous = np.ascontiguousarray(arr.T)

Conclusion

Use .T for quick 2D transpositions—it’s clean and readable. Reach for numpy.transpose() or the .transpose() method when you need custom axis reordering for higher-dimensional arrays. Remember that all transpose operations return views, so modifications propagate back to the original array unless you explicitly copy. For image processing and deep learning workflows, memorize the common axis permutations: (2, 0, 1) converts HWC to CHW, and (1, 2, 0) reverses it.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.