Skip to content
Home Python NumPy Shape Manipulation — reshape, flatten, ravel, transpose

NumPy Shape Manipulation — reshape, flatten, ravel, transpose

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Python Libraries → Topic 27 of 51
How to reshape, flatten, and transpose NumPy arrays.
⚙️ Intermediate — basic Python knowledge assumed
In this tutorial, you'll learn
How to reshape, flatten, and transpose NumPy arrays.
  • reshape() returns a view when memory is contiguous — mutating the result mutates the original.
  • flatten() always copies; ravel() returns a view when possible.
  • transpose() and .T always return views — no data is copied.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • reshape() returns a view when data is contiguous in memory; else a copy.
  • flatten() always returns a copy, safe for independent modifications.
  • ravel() returns a view when possible; faster but can mutate original.
  • transpose() returns a view with reordered axes, never copies data.
  • squeeze() and expand_dims() adjust dims of size 1 and 0, zero memory overhead.
🚨 START HERE
Quick Shape Debug Commands
Commands to diagnose shape, memory layout, and contiguity issues
🟡Need to verify memory layout before ravel/reshape
Immediate ActionCheck contiguity flags
Commands
arr.flags['C_CONTIGUOUS']
arr.flags['F_CONTIGUOUS']
Fix NowIf not contiguous, use np.ascontiguousarray(arr) then call ravel() or reshape().
🟡Array shape doesn't match expectation after operation
Immediate ActionPrint current shape
Commands
arr.shape
arr.ndim
Fix NowUse arr.reshape(-1, desired_cols) to let NumPy infer row count.
🟡Memory usage exploded after reshape/flatten
Immediate ActionCheck if operation made a copy
Commands
arr = arr.reshape(new_shape); np.shares_memory(arr, original_arr)
sys.getsizeof(arr) vs original
Fix NowIf copy is unintended, use axis=-1 in reshape (if possible) to keep view.
🟡transpose caused array to become non-contiguous
Immediate ActionCheck stride ordering
Commands
arr.strides
arr.flags['C_CONTIGUOUS']
Fix NowCall np.ascontiguousarray(arr) before further operations that require contiguity.
Production IncidentSilent Corruption: How ravel() Sabotaged Training DataEngineering team spent weeks debugging a 3% performance degradation in their CNN model, only to find that ravel() had silently corrupted the training data pipeline.
SymptomModel accuracy plateaued at 67% after adding a custom augmentation layer. No errors, just consistently poor convergence.
Assumptionflatten() and ravel() are interchangeable; since performance mattered, ravel() was used to minimize overhead.
Root causeThe augmentation pipeline produced a non-contiguous array (after transpose + slicing). ravel() returned a view that pointed to the same memory, but later in-place modifications in the model preprocessing (normalize) overwrote data that was still referenced by the augmentation cache. The view introduced unintended aliasing.
FixReplace ravel() with flatten().copy() in the data pipeline, or ensure the array is contiguous before calling ravel() using np.ascontiguousarray(). Added a unit test that checks array.flags.c_contiguous before allowing ravel() in production paths.
Key Lesson
Never assume ravel() returns a view — it can, and when it does, mutations to the view affect the original.In data pipelines where data integrity is critical, use flatten() or explicitly copy the array.Always validate contiguity flags (arr.flags['C_CONTIGUOUS']) before using ravel() in shared-memory contexts.
Production Debug GuideSymptom → Action reference for common NumPy shape manipulation issues
ValueError: cannot reshape array of size 12 into shape (3,5)Total elements must match. Check source array shape: arr.shape. Use -1 for the dimension you want NumPy to compute.
Unexpected mutation of original array after using reshape()Check contiguity: arr.flags['C_CONTIGUOUS']. If True, reshape returned a view. Use .copy() if independent copy needed.
MemoryError when calling flatten() on a large arrayflatten() always copies memory. For large arrays, consider ravel() if you don't need a separate copy, or use .reshape(-1) which may produce a view.
Broadcasting error — shapes (3,1) and (1,4) don't align for matrix multiplicationReshape or transpose dimensions. Use reshape(3,4) or transpose to align axes. Check broadcasting rules: trailing dims must match or be 1.

Reshaping arrays is one of the most frequent NumPy operations, especially when preparing data for machine learning models. A linear layer expects (batch, features). A convolution expects (batch, channels, height, width). Getting the shape right without introducing bugs requires understanding which operations copy data and which do not.

This guide covers the core shape manipulation functions, the view/copy rules for each, and the practical patterns that come up in real code.

reshape — Changing Shape Without Changing Data

reshape() returns a view when the data is contiguous in memory (which it usually is for freshly created arrays). Use -1 as a wildcard dimension and NumPy computes it from the total element count. This is the most common way to restructure data for ML model inputs.

reshape_demo.py · PYTHON
12345678910111213
import numpy as np

a = np.arange(12)  # shape (12,)

print(a.reshape(3, 4))   # (3, 4)
print(a.reshape(2, 6))   # (2, 6)
print(a.reshape(2, -1))  # (2, 6) — -1 inferred
print(a.reshape(3, 2, 2)) # (3, 2, 2)

# Common ML pattern: add batch dimension
single = np.random.randn(28, 28)       # one image
batch  = single.reshape(1, 28, 28)     # one image in a batch
print(batch.shape)  # (1, 28, 28)
▶ Output
(3, 4) view of original data
(1, 28, 28)
Mental Model
The Rubber Band Analogy
Think of reshape as stretching a rubber band – same material, different shape – but only if the band is in one contiguous piece.
  • If array is contiguous in memory, reshape just changes the stride/offset metadata — no data movement.
  • If non-contiguous, NumPy silently creates a copy – now you have two rubber bands.
  • Use -1 dimension to let NumPy calculate the remaining size automatically.
📊 Production Insight
After transpose or fancy indexing, arrays often become non-contiguous.
Calling reshape on a non-contiguous array forces a copy — performance hit, but also breaks the view contract.
Rule: check arr.flags['C_CONTIGUOUS'] before relying on view behavior.
🎯 Key Takeaway
reshape() is a metadata change when possible.
Always verify contiguity before assuming view.
Use -1 for automatic dimension calculation.
Choosing reshape strategy
IfNeed to change shape and fine with potential copy
UseUse reshape() directly — fastest.
IfMust guarantee no copy
UseCall ascontiguousarray() first, then reshape().
IfNeed to enforce copy
UseUse arr.reshape(shape).copy() explicitly.
IfOnly one dimension unknown
UseUse -1 as wildcard.

flatten vs ravel — Both Give 1D, Different Memory

flatten() always returns a new array with its own memory. ravel() returns a view when the array is contiguous, else it returns a flattened copy. For most use cases ravel() is faster and memory-efficient, but if you need a guaranteed independent copy that won't mutate the original, use flatten().

flatten_vs_ravel.py · PYTHON
123456789101112
import numpy as np

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

f = m.flatten()   # copy — always
r = m.ravel()     # view when possible

f[0] = 99
print(m[0, 0])  # 1 — flatten copy not linked

r[0] = 99
print(m[0, 0])  # 99 — ravel view is linked
▶ Output
1
99
⚠ Hidden dependencies with ravel()
In a data pipeline, if you mutate the result of ravel() you're mutating the original array. This can cause subtle bugs where a seemingly isolated transformation corrupts upstream data. Always use flatten() when you need independence.
📊 Production Insight
flatten() always doubles memory — problematic for arrays > 1GB.
ravel() is zero-copy when contiguous, but introduces aliasing.
Rule: Use ravel() in read-only contexts; use flatten().copy() in read-write pipelines.
🎯 Key Takeaway
flatten() copies every time — safe but costly.
ravel() is fast but can alias.
When in doubt, flatten explicitly.
flatten vs ravel decision
IfData is read-only after flattening
UseUse ravel() — faster, less memory.
IfWill modify the result and must not affect original
UseUse flatten() — guaranteed copy.
IfUncertain about contiguity and need safety
UseUse flatten() or ascontiguousarray + ravel.

transpose — Reordering Axes

transpose() reverses the order of axes by default (equivalent to .T). You can also pass a tuple to specify the exact axis order. It always returns a view — no data is copied, only the strides and shape metadata are rearranged. This is extremely fast but the result is non-contiguous in memory.

transpose_demo.py · PYTHON
12345678910
import numpy as np

a = np.arange(24).reshape(2, 3, 4)
print(a.shape)                   # (2, 3, 4)
print(a.T.shape)                 # (4, 3, 2) — reversed
print(a.transpose(0, 2, 1).shape)  # (2, 4, 3) — swap last two axes

# .T is just shorthand for .transpose()
matrix = np.ones((3, 5))
print(matrix.T.shape)  # (5, 3)
▶ Output
(2, 3, 4)
(4, 3, 2)
(2, 4, 3)
(5, 3)
🔥Transpose never copies – but watch the performance
Because transpose returns a view, the data stays in place. However, accessing elements in the transposed order may be slower due to cache misses if the memory layout doesn't match the iteration order. For performance-critical loops, consider converting to contiguous with np.ascontiguousarray().
📊 Production Insight
Transposed arrays break C-contiguity — subsequent ops like .reshape() will trigger copies.
Rule: If you transpose and then need to reshape, combine both operations in one call to avoid an intermediate copy.
🎯 Key Takeaway
transpose() is always a view — no copy.
Result is non-contiguous — subsequent calls may copy.
Explicit axis order avoids surprises.
When to use transpose vs changing stride manually
IfNeed to swap two axes for broadcasting
UseUse np.transpose(arr, axes) with explicit order.
IfReverse all axes
UseUse arr.T or np.transpose(arr).
IfNeed C-contiguous output for further operations
UseTranspose then call np.ascontiguousarray() or use np.moveaxis which may copy.

squeeze and expand_dims — Manage Singleton Dimensions

squeeze() removes dimensions of size 1 from the shape. expand_dims() inserts a new dimension of size 1 at a specified axis. Both return views when possible. They are essential for aligning array shapes before operations like concatenation or broadcasting.

squeeze_expand.py · PYTHON
123456789
import numpy as np

a = np.zeros((1, 3, 1, 4))   # shape with two size-1 dims
print(a.squeeze().shape)     # (3, 4)
print(a.squeeze(axis=0).shape)  # (3, 1, 4) — remove only axis 0

b = np.array([1, 2, 3])  # shape (3,)
print(np.expand_dims(b, axis=0).shape)  # (1, 3)
print(np.expand_dims(b, axis=1).shape)  # (3, 1)
▶ Output
(3, 4)
(3, 1, 4)
(1, 3)
(3, 1)
💡Broadcasting helper
Use expand_dims to make arrays broadcastable without manual reshaping. For example, adding a batch dimension: np.expand_dims(single_image, axis=0) gives shape (1, H, W).
📊 Production Insight
squeeze() on selective axes may fail if the axis size is not 1.
expand_dims does not allocate new memory — it's just a view with a new stride.
Rule: Use squeeze(axis=...) when you know the dimension is singleton; use np.squeeze(arr) with no axis to remove all.
🎯 Key Takeaway
squeeze() removes size-1 dims without copying.
expand_dims() adds them without memory overhead.
Both return views — use them freely.
When to squeeze or expand
IfArray has unnecessary singleton dims before ML model
UseUse squeeze() to reduce shape.
IfNeed to add dim to make arrays broadcastable
UseUse expand_dims or indexing: arr[None, ...].
IfOnly remove specific singleton dims
UseUse squeeze(axis=target).

Reshaping in Practice: ML Data Pipeline Patterns

In machine learning, shape manipulation is used constantly to convert between different data layouts. Common patterns: (samples, features) → (batch, channels, height, width) for CNNs; adding batch dimensions; flattening final layers; transposing from (batch, seq, features) to (seq, batch, features) for recurrent networks. Knowing which operations are views vs copies directly impacts memory budgets and training speed.

ml_reshape_patterns.py · PYTHON
12345678910111213
import numpy as np

# 1. Load flat CSV, reshape into images
flat = np.random.randn(1000 * 28 * 28)  # 784000 items
images = flat.reshape(-1, 28, 28)        # (1000, 28, 28)
# 2. Add channel dim for CNN
images_cnn = np.expand_dims(images, axis=1)  # (1000, 1, 28, 28)
# 3. Transpose for sequence models: (batch, time, features) -> (time, batch, features)
batch_seq_feat = np.random.randn(32, 50, 128)
seq_batch_feat = np.transpose(batch_seq_feat, (1, 0, 2))  # (50, 32, 128)
# 4. Flatten final conv features for dense layer
conv_out = np.random.randn(32, 64, 7, 7)  # batch=32, channels=64, H=7, W=7
flat_features = conv_out.reshape(32, -1)  # (32, 64*7*7=3136)
Mental Model
Shape as a contract between layers
Every ML layer has an expected shape contract. Reshape is the glue that ensures compatibility without changing the underlying data.
  • Reshape changes only the metadata — data stays same.
  • Using -1 lets NumPy auto-calculate one dimension.
  • Avoid chaining transpose + reshape unless you need the copy.
📊 Production Insight
Large multi-dimensional arrays that are non-contiguous after transpose cause reshape to copy, doubling memory usage.
For a model with activation maps of shape (batch, 1024, 14, 14), a forced copy can add 200MB+ per batch.
Rule: Whenever possible, design the pipeline to produce arrays in the final required memory layout (C-contiguous).
🎯 Key Takeaway
Plan memory layout before building the pipeline.
Views are free; copies cost memory.
Combine operations to avoid intermediate copies.
Reshape strategy for ML pipelines
IfNeed to go from flat to multi-dimensional
UseUse reshape(-1, dim1, dim2) — view if contiguous.
IfNeed to add batch dimension
UseUse expand_dims(arr, axis=0) — zero copy.
IfNeed to reorder axes for a different model
UseUse transpose; then ascontiguousarray if reshaping follows.
🗂 Shape Manipulation Functions Comparison
View vs Copy and Performance Impact
FunctionReturns View?Memory AllocationUse Case
reshape()Yes (if contiguous)None (unless non-contiguous)General shape change
flatten()NoAlways new arraySafe 1D copy
ravel()Yes (if contiguous)None (if view), else copyFast 1D, read-only
transpose()AlwaysNoneReordering axes
squeeze()YesNoneRemove size-1 dims
expand_dims()YesNoneAdd size-1 dim

🎯 Key Takeaways

  • reshape() returns a view when memory is contiguous — mutating the result mutates the original.
  • flatten() always copies; ravel() returns a view when possible.
  • transpose() and .T always return views — no data is copied.
  • Use -1 in reshape() as a wildcard to let NumPy compute one dimension automatically.
  • squeeze() and expand_dims() are how you fix mismatched shapes before broadcasting.
  • Always check arr.flags['C_CONTIGUOUS'] before relying on view semantics.

⚠ Common Mistakes to Avoid

    Assuming reshape always returns a view
    Symptom

    After transpose, reshape on the result triggers a silent copy; the original array remains unchanged, but the developer expected view semantics and may see unexpected memory spikes.

    Fix

    Check arr.flags['C_CONTIGUOUS'] before relying on view. Use ascontiguousarray if needed.

    Using ravel() in a data pipeline where mutations occur
    Symptom

    Modifying the flattened array causes the original data to change, leading to hard-to-find bugs in training data.

    Fix

    Use flatten() instead of ravel() when you need an independent copy. Or copy explicitly: ravel().copy().

    Using transpose repeatedly in a loop causing excessive cache misses
    Symptom

    Performance degradation – accessing elements in transposed order leads to strided memory access that thrashes the cache.

    Fix

    After transpose, call ascontiguousarray() if you'll iterate over elements frequently.

    Forgetting that squeeze removes all size-1 dims by default
    Symptom

    squeeze() without axis unexpectedly removes more dimensions than intended, breaking downstream shape assumptions.

    Fix

    Always specify axis when you want to preserve other singleton dims: squeeze(axis=0) only removes the first axis.

Interview Questions on This Topic

  • QWhat is the difference between flatten() and ravel() in NumPy?Mid-levelReveal
    flatten() always returns a copy of the array in 1D, allocating new memory. ravel() returns a flattened view when the array is contiguous in memory; if not contiguous, it returns a copy. Performance-wise, ravel() is faster and memory-efficient for contiguous arrays because it doesn't allocate new memory. Use flatten() when you need an independent copy that won't affect the original array.
  • QWhen does reshape() return a view vs a copy?Mid-levelReveal
    reshape() returns a view when the underlying data is contiguous in memory (C-order or Fortran-order). After operations like transpose, fancy indexing, or slicing, the array may become non-contiguous, in which case reshape() will return a copy. You can check contiguity with arr.flags['C_CONTIGUOUS']. If you need to guarantee a view, first make the array contiguous using np.ascontiguousarray(arr).
  • QHow does transpose affect memory layout and subsequent operations?SeniorReveal
    transpose() always returns a view, so no data is copied. However, the resulting array is non-contiguous in memory (its strides are rearranged). This means subsequent operations like reshape() or flatten() may cause a copy because they require contiguity. Also, iterating over a transposed array is slower due to strided memory access. For performance-critical code, call np.ascontiguousarray() after transpose if you plan to do further reshaping or iteration.

Frequently Asked Questions

When does reshape() return a copy instead of a view?

When the array is not contiguous in memory — for example, after a transpose or certain fancy indexing operations. You can check with arr.flags['C_CONTIGUOUS']. If reshape cannot produce a view, it silently creates a copy.

What is the difference between shape (n,) and shape (1, n)?

Shape (n,) is a 1-D array. Shape (1, n) is a 2-D array with one row. They broadcast differently. Most NumPy operations accept both, but operations that expect a matrix (like dot product dimension rules) care about the distinction.

Is it safe to use ravel() in a multi-threaded environment?

If the array is contiguous, ravel() returns a view that shares memory. In multi-threaded code, if one thread modifies the raveled array and another reads the original, you'll get a data race. Use flatten() or explicitly copy to avoid shared state.

How can I avoid a copy when reshaping a transposed array?

First make the array contiguous with np.ascontiguousarray(arr), then reshape. However, this still copies if the array was non-contiguous. The only way to guarantee no copy is to reshape before transpose or design the data layout to be C-contiguous from the start.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousNumPy Indexing and Slicing — Beyond the BasicsNext →NumPy Mathematical Functions — ufuncs, aggregations and statistics
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged