The same numbers, read a different way - and why reshaping usually costs nothing at all.
Overview
Three numbers
The shape of an array tells you its size along each axis. A 2-D array with three rows and four columns has shape (3, 4). A 1-D array of ten elements has shape (10,). A scalar has an empty shape. These three numbers -- shape, ndim, and size -- are the first things to check when an operation gives an unexpected result.
ndim is the number of axes, which is just the length of the shape tuple. size is the total number of elements, which is the product of the shape entries. Both are derived from shape, but having them as separate attributes makes quick checks easier.
Reshaping changes the shape without changing the data. A (12,) array can become (3, 4) or (2, 6) or (4, 3). The total number of elements must stay the same. Reshape does not move any data in memory. It just changes the description of how the bytes are interpreted. That is why reshape is fast and why the result is a view of the original array.
The negative-one shorthand lets you specify all dimensions but one. reshape(3, -1) on a 12-element array gives (3, 4) because NumPy infers that the missing dimension must be four. This is convenient when one dimension is known and the other depends on the data size.
The distinction between (n,) and (n, 1) matters more than it looks. A 1-D array and a column vector have different numbers of dimensions. Broadcasting rules treat them differently. Some functions return one shape when you expected the other. Knowing the difference prevents a class of shape-mismatch errors.
This module covers shape, ndim, size, reshape, ravel, flatten, and the newaxis trick for adding dimensions. The examples focus on small arrays where you can verify the layout by printing.
shape is a tuple showing the extent of each axis. ndim is how many axes there are. This Is just len(shape). size is the total number of elements. This Is the product of the shape.
When an step complains about shapes, printing all three of these for both operands answers the question most of the time. It's worth making that reflex.
The empty tuple is a valid shape: np.array(5).shape is (), a zero-dimensional array holding one value. That comes up when an aggregation reduces all away.
Worth knowing
shape is a tuple, ndim is its length, and size is the product of its entries.
Reshaping returns a view where it can: the numbers do not move, so writing through it changes the original.
-1 lets NumPy work out one dimension. Prefer it to a hard-coded length that a later change will invalidate.
NumPy is row-major by default: the last axis varies fastest. That determines what reshape and ravel produce.
ravel gives a view when it can; flatten always copies. Use ravel to read, flatten when you need independence.
reshape(-1, 1) and np.newaxis add a length-1 axis. Most shape errors are one of those in the wrong position.
Shape and Reshape
The same numbers read a other way, and why it often costs nothing.
shape, ndim and size
Three numbers describe the layout. They are the first things to print when something does not fit.
example_01.pyNumPy
import numpy as np
for a in [np.arange(6),
np.arange(6).reshape(2, 3),
np.arange(24).reshape(2, 3, 4)]:
print("shape %-12s ndim %d size %2d itemsize %d nbytes %d"
% (str(a.shape), a.ndim, a.size, a.itemsize, a.nbytes))
print()
print("size is the product of the shape:")
a = np.arange(24).reshape(2, 3, 4)
print(" 2 * 3 * 4 =", 2 * 3 * 4, "==", a.size)
Output
Reshaping does not move anything
The numbers stay where they are. Only the description of how to walk them changes — which is why it is effectively free.
example_02.pyNumPy
import numpy as np
a = np.arange(12)
b = a.reshape(3, 4)
print("flat:", a)
print("as 3x4:")
print(b)
print()
print("shares memory :", np.shares_memory(a, b))
print("b.base is a :", b.base is a)
print()
b[0, 0] = 99
print("changing b[0,0] changed a:", a[:4])
Output
-1 means work it out
One dimension can be left to NumPy. It is the difference between code that survives a change in length and code that does not.
example_03.pyNumPy
import numpy as np
a = np.arange(12)
print("reshape(3, 4) :", a.reshape(3, 4).shape)
print("reshape(3, -1) :", a.reshape(3, -1).shape)
print("reshape(-1, 2) :", a.reshape(-1, 2).shape)
print("reshape(-1) :", a.reshape(-1).shape, "<- back to flat")
try:
a.reshape(5, -1)
except ValueError as e:
print()
print("12 does not divide by 5:", type(e).__name__)
print(" ", e)
Output
Row-major order is the default
The last axis varies fastest. Knowing that makes reshape results predictable instead of surprising.
example_04.pyNumPy
import numpy as np
a = np.arange(6)
print("flat :", a)
print()
print("C order (default) - fill rows first:")
print(a.reshape(2, 3))
print()
print("F order (Fortran) - fill columns first:")
print(a.reshape(2, 3, order="F"))
print()
print("ravel reads back in the same order it filled:")
m = a.reshape(2, 3)
print(" ravel() :", m.ravel())
print(" ravel(order=F) :", m.ravel(order="F"))
Output
ravel, flatten and the difference
Both give you 1-D. One is a view when it can be, the other always copies.
example_05.pyNumPy
import numpy as np
m = np.arange(6).reshape(2, 3)
r = m.ravel()
f = m.flatten()
print("ravel shares memory:", np.shares_memory(m, r))
print("flatten shares memory:", np.shares_memory(m, f))
r[0] = 99
print()
print("after r[0] = 99:")
print(" m[0,0]:", m[0, 0], "<- ravel wrote through")
f[1] = 77
print(" m[0,1]:", m[0, 1], "<- flatten did not")
print()
print("Use ravel when you only read, flatten when you need an independent copy.")
Output
Adding and removing length-1 axes
Most shape errors in real code are an axis of size one in the wrong place. These are the tools for fixing that.
example_06.pyNumPy
import numpy as np
a = np.arange(3)
print("a :", a.shape)
col = a.reshape(-1, 1)
row = a.reshape(1, -1)
print("as a column :", col.shape)
print(col)
print("as a row :", row.shape, row)
print()
print("newaxis does the same thing, more readably:")
print(" a[:, np.newaxis].shape :", a[:, np.newaxis].shape)
print(" a[np.newaxis, :].shape :", a[np.newaxis, :].shape)
print()
print("squeeze removes every length-1 axis:")
odd = np.arange(3).reshape(1, 3, 1)
print(" before:", odd.shape, " after:", odd.squeeze().shape)
Output
Reshaping is free
An array is a flat block of memory plus a description of how to walk it. reshape changes the description and leaves the memory alone.
That means it returns a view, not a copy. Writing through the reshaped array changes the first. There's only one set of numbers. np.shares_memory confirms it. The second editor shows the write going both ways.
This is the first appearance of a theme that runs through the whole track. Which has a module of its own. Many NumPy steps give you a other window onto the same data rather than new data.
Reshape can only return a view when the result can be given by regular strides. If it can't. Often after a transpose. NumPy copies instead, silently. If you need to know which happened, check .base or np.shares_memory.
-1
One dim can be -1, meaning "work this one out from the others and the total size".
a.reshape(3, -1) says three rows. But Many columns that takes. It's better than hard-coding the second number. It keeps working when the input length changes.
Only one axis can be -1. Two would be ambiguous. And the total must divide just. Reshaping twelve elements into rows of five raises. This Is the correct action and a handy early warning that an upstream length isn't what you assumed.
Row-major order
NumPy walks the last axis fastest by default. This Is called C order or row-major. Filling a (2, 3) array from [0, 1, 2, 3, 4, 5] gives rows [0, 1, 2] and [3, 4, 5].
Fortran order fills the first axis fastest, giving columns instead. reshape and ravel both take an order arg.
You mostly don't need to think about this until you do. Interfacing with code that expects column-major layout, reading a binary file written by a thing else, or debugging a reshape that made a transposed-looking result. Knowing that the default is "last axis fastest" makes those predictable rather than mysterious.
ravel and flatten
Both return a 1-D version. The gap is copying.
ravel returns a view when the layout permits. A copy when it doesn't. flatten always copies.
So ravel is cheaper and shares memory. flatten is safe and split. Use ravel when you're only reading. flatten when you intend to modify the result without touching the first. Or when you want a promise rather than a maybe.
The fifth editor shows the gap right: writing through the ravelled array changes the source, writing through the flattened one doesn't.
Length-1 axes
A large share of real shape errors are an axis of size one in the wrong spot, often when a column of data needs to be a column rather than a flat sequence.
a.reshape(-1, 1) makes a column, a.reshape(1, -1) makes a row. np.newaxis in an index does the same thing and reads better in place: a[:, np.newaxis] says clearly that a new axis is being inserted at the end.
squeeze goes the other way, dropping each axis of length one. That is handy for cleaning up after an step that kept dims you no longer need. And mildly dangerous in library code. It drops axes you might have been leaning on when a dim happens to be one.
Getting these right matters most for broadcasting. This Is the next module but one. Where the gap between shape (3,) and shape (3, 1) decides whether you get the answer you wanted or a much larger array you didn't.
The invariant
a.size is the product of a.shape, always. each reshape must preserve it. This Is why an hopeless reshape raises rather than truncating or padding.
That single constraint answers most "will this work" questions before you run anything. A 24-element array can become (2,12), (3,8), (2,3,4) or (24,). It can't become (5,5).
a.ndim is len(a.shape), and is worth checking when writing functions that accept either a single sample or a batch of them.
When reshape has to copy
reshape returns a view when the requested shape can be expressed as a stride pattern over the existing memory. A copy when it can't.
For a freshly created contiguous array, it's always a view.
After a transpose, it often isn't. The transposed array reads the buffer column-first. Flattening it in row-major order means gathering scattered values. Which needs new memory.
NumPy doesn't tell you which happened. np.shares_memory(a, b) does.
If you need a promise in the other direction, a.shape = (3, 4) assigns the shape in place and raises if a view is hopeless. That is a handy assertion. It fails loudly rather than silently copying. This Is just what you want in code where an accidental copy of a large array would be a speed bug.
Row-major, column-major, and order=
C order, the default, means the last axis varies fastest. Reading a.ravel() on a (2,3) array gives the first row then the second.
Fortran order means the first axis varies fastest. Reading gives the first column then the second.
reshape and ravel both take order="F" to work column-first without changing the underlying data's actual layout. This Is at times the clearest way to express an step.
Genuine Fortran-sorted arrays — created with np.asfortranarray or order="F" — matter mainly when interfacing with Fortran or MATLAB-derived libraries. Some LAPACK routines are faster on them because they avoid an inner transpose.
For everyday work, staying in C order and not thinking about it's the right default. The time to care is when flags tells you an array isn't contiguous and a thing downstream is copying.
reshape versus resize
They sound like a pair and aren't.
np.reshape returns a new view or copy with a other shape, leaving the first alone. The size must match.
a.resize(shape) modifies the array in place and can change the size, padding with zeros or truncating. It also refuses to run if the array shares memory with anything else. This Is a sensible protection and an occasional annoyance.
np.resize(a, shape) — the function, not the method — is other again. It repeats the data cyclically to fill the new shape.
Three likewise named steps with three other actions. reshape covers nearly all. The other two are worth recognising when you meet them rather than reaching for.
Length-1 axes, and why they keep appearing
np.newaxis (which is just None) inserts an axis of length 1. np.expand_dims does the same thing as a function call. np.squeeze drops each length-1 axis, or a named one.
These exist almost fully to serve broadcasting. A length-1 axis is the one broadcasting will stretch. Inserting one is how you say "align this against that axis".
They also appear at the boundaries of libraries. A model that expects a batch gets a single sample wrapped with x[None]. A result that comes back as (n, 1) gets squeezed to (n,) before being handed to a thing expecting a vector.
A caution on squeeze with no args: it drops *all* length-1 axes. This Means a batch of one sample loses its batch dim too. In code that handles var batch sizes, that turns into an intermittent bug that only appears when the batch happens to hold one item. Naming the axis. squeeze(axis=1) — makes it safe.
Debugging shape errors
Shape errors are the most common failure in array code. They're also the easiest to diagnose. The error message holds both shapes.
The routine that resolves nearly all of them:
Print the shapes of the inputs. Not the arrays. The shapes. Most of the time the mismatch is at once visible and the fix is obvious.
Ask which axis each one is supposed to represent. Shape errors are often a semantic mistake. Samples and features swapped, a batch axis missing. Rather than an math one.
Check whether a reduction removed an axis you needed. keepdims=True is the fix when the answer is yes.
And check ndim when a function accepts both single items and batches. A great many "this worked yesterday" bugs are a (3,) arriving where a (1, 3) was expected.
Common shape mistakes
**given reshape reorders.** It doesn't rearrange values, only how they're grouped. If the values come out in an surprise setup, the data was in a other order than assumed. Reshaping again won't fix it — a transpose might.
**Using reshape to transpose.** a.reshape(4, 3) on a (3, 4) array isn't a.T. Both give a (4, 3) result and they hold other values in other places. This is a truly common bug because the shapes match and nothing raises.
**squeeze() with no arg** in code that handles var batch sizes. It drops each length-1 axis. A single-item batch loses its batch dim.
**Leaning on reshape returning a view.** It often does and at times doesn't. np.shares_memory answers it. Assigning to .shape enforces it.
Losing an axis to an int index and then being surprised when broadcasting fails. a[:, 0] gives (3,). a[:, 0:1] gives (3, 1).
Questions people ask about Shape and Reshape
What does -1 mean in reshape? It asks NumPy to infer that axis length from the total size. a.reshape(-1, 3) gives rows of three, whatever the count.
Does reshape copy the data? Usually not, it returns a view when the memory layout allows. If it cannot, such as after certain transposes, it quietly copies.
What is the difference between ravel and flatten? ravel returns a view when possible, while flatten always returns a copy. Use flatten only when you need to modify the result independently.
Why do I get 'cannot reshape array of size 12 into shape (5, 3)'? The product of the new shape must equal the element count. 5 times 3 is 15, not 12.
What is a shape of (5,) versus (5, 1)? The first is a flat vector with one axis. The second is a column with two axes, and the two broadcast very differently.
How do I add a length-1 axis? Index with None, as in a[:, None], or call np.expand_dims. reshape also works.
Reading a shape error
The error message holds all needed, and reading it beats guessing.
"operands couldn't be broadcast together with shapes (3,4) (3,)" names both shapes. Align them from the right: 4 against 3. They aren't equal and neither is 1. It fails. The fix is either keepdims=True on what made the (3,), or an clear [:, None].
"can't reshape array of size 12 into shape (5,5)" is an math statement: 12 isn't 25. A thing upstream made a other amount of data than expected. The reshape is reporting it rather than causing it.
"matmul: Input operand 1 has a mismatch in its core dim" means the inner dims of a mat product don't agree. Print both shapes and check which axis is meant to be the shared one.
In each case the productive move is the same. Print the shapes of the inputs, decide what each axis is supposed to mean. Fix the one that is wrong. Shape errors are almost always semantic. A transposed input, a missing batch axis, a reduction that removed a thing — rather than math.
Check yourself
0 of 4
Answer without scrolling back up.
Does `reshape` copy the data?
The numbers do not move - only the description of how to walk them. Writing through the reshaped array changes the original.
What does `-1` mean in a reshape?
Only one axis may be -1, and the total must divide exactly. It keeps code working when the input length changes.
What is the difference between `ravel` and `flatten`?
So writing through a ravelled array can change the source. Use ravel to read, flatten when you need independence.
In default C order, which axis varies fastest?
Row-major: the last axis varies fastest. That is what makes reshape and ravel results predictable.
Cheat sheet
Shape and Reshape
Reshape a NumPy array without copying data. Use reshape, ravel and newaxis to rearrange dimensions while the underlying memory stays put.
Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.