Fix NumPy shape errors by identifying what each dimension means, comparing the input shapes and checking the intended result. These examples show common failures, a suitable repair and a value check so a silent broadcasting mistake is easier to catch.
Intentional errors are caught in the runnable examples. Exact exception wording can differ by NumPy version.
Show Table of ContentsWrite down what each dimension represents, then inspect both operands before choosing a fix. Axis zero does not inherently mean samples: that is a convention you assign to the data. Use small rectangular arrays to distinguish rows from columns.
import numpy as np
def expect_error(label, operation, error_type=ValueError):
try:
operation()
except error_type as error:
print(label + ':', type(error).__name__)
else:
raise AssertionError('Expected ' + error_type.__name__)
a = np.arange(12).reshape(3, 4)
print("shape:", a.shape, "ndim:", a.ndim, "size:", a.size)
print(a)
assert a.shape == (3, 4)Expected output
shape: (3, 4) ndim: 2 size: 12
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]A reshape changes dimensions without inventing values. The target dimension product must equal size. One -1 can infer a dimension when the other dimensions divide the size. See reshape().
a = np.arange(10)
expect_error('10 elements into 3 by 4', lambda: a.reshape(3, 4))
print("Explicit:", a.reshape(2, 5).shape)
print("Inferred:", a.reshape(-1, 5).shape)
expect_error('Two inferred dimensions', lambda: a.reshape(-1, -1))
assert a.reshape(-1, 5).shape == (2, 5)Expected output
10 elements into 3 by 4: ValueError
Explicit: (2, 5)
Inferred: (2, 5)
Two inferred dimensions: ValueErrorCompare dimensions from the right: each pair must be equal or one must be 1. For (3, 4) plus (3,), the trailing 4 and 3 conflict. To add one value per row, reshape the vector to (3, 1). Review broadcasting.
a = np.zeros((3, 4))
b = np.array([10, 20, 30])
expect_error('Trailing dimensions conflict', lambda: a + b)
b_col = b[:, None]
result = a + b_col
print("Column-vector shape:", b_col.shape)
print(result)
np.testing.assert_array_equal(result[:, 0], b)
assert result.shape == a.shapeExpected output
Trailing dimensions conflict: ValueError
Column-vector shape: (3, 1)
[[10. 10. 10. 10.]
[20. 20. 20. 20.]
[30. 30. 30. 30.]]On a square array, a row-offset vector can accidentally align with columns without an exception. Check actual values and intended meaning, not only whether code runs. Adding a singleton axis makes the row intent explicit.
a = np.zeros((3, 3))
offsets = np.array([10, 20, 30])
wrong = a + offsets
correct = a + offsets[:, None]
print("Column offsets:")
print(wrong)
print("Intended row offsets:")
print(correct)
assert not np.array_equal(wrong, correct)
np.testing.assert_array_equal(correct[:, 0], offsets)Expected output
Column offsets:
[[10. 20. 30.]
[10. 20. 30.]
[10. 20. 30.]]
Intended row offsets:
[[10. 10. 10.]
[20. 20. 20.]
[30. 30. 30.]]axis=0 combines rows and leaves one value per column. axis=1 combines columns and leaves one value per row. keepdims preserves reduced axes with length one when broadcasting the result back. See axis reduction.
x = np.arange(12, dtype=float).reshape(3, 4)
column_means = x.mean(axis=0)
row_means = x.mean(axis=1, keepdims=True)
columns_centered = x - column_means
rows_centered = x - row_means
print("Column mean shape:", column_means.shape)
print("Row mean shape:", row_means.shape)
print("Row-centered values:")
print(rows_centered)
np.testing.assert_allclose(columns_centered.mean(axis=0), 0)
np.testing.assert_allclose(rows_centered.mean(axis=1), 0)Expected output
Column mean shape: (4,)
Row mean shape: (3, 1)
Row-centered values:
[[-1.5 -0.5 0.5 1.5]
[-1.5 -0.5 0.5 1.5]
[-1.5 -0.5 0.5 1.5]]For a 2D array the valid axis integers are 0, 1, -1 and -2. Negative axes count from the end. Check ndim after slicing, because selecting a single row often removes an axis.
a = np.arange(12).reshape(3, 4)
expect_error('Invalid reduction axis', lambda: a.sum(axis=2), np.exceptions.AxisError)
row = a[0]
print("Single row shape:", row.shape)
expect_error('Row is now one dimensional', lambda: row.sum(axis=1), np.exceptions.AxisError)
kept = a[0:1]
print("Row with axis retained:", kept.shape)
print("Row total:", kept.sum(axis=1))
assert kept.shape == (1, 4)Expected output
Invalid reduction axis: AxisError
Single row shape: (4,)
Row is now one dimensional: AxisError
Row with axis retained: (1, 4)
Row total: [6]Inputs must have matching ranks and matching dimensions except along the concatenation axis. Changing the axis can fix a valid join, but should match the intended row or feature meaning.
a = np.ones((2, 3))
b = np.zeros((2, 3))
print("More rows:", np.concatenate([a, b], axis=0).shape)
print("More columns:", np.concatenate([a, b], axis=1).shape)
c = np.zeros((2, 4))
expect_error('Different column counts for vertical join', lambda: np.concatenate([a, c], axis=0))
print("Valid horizontal join:", np.concatenate([a, c], axis=1).shape)Expected output
More rows: (4, 3)
More columns: (2, 6)
Different column counts for vertical join: ValueError
Valid horizontal join: (2, 7)A row shaped (3,) cannot be concatenated vertically with a (2, 3) table. Add a leading dimension to create (1, 3). Flattening everything would remove the table structure.
a = np.arange(6).reshape(2, 3)
row = np.array([10, 20, 30])
expect_error('Different ranks', lambda: np.concatenate([a, row], axis=0))
joined = np.concatenate([a, row[None, :]], axis=0)
print(joined)
assert joined.shape == (3, 3)
np.testing.assert_array_equal(joined[-1], row)Expected output
Different ranks: ValueError
[[ 0 1 2]
[ 3 4 5]
[10 20 30]]stack requires equal input shapes. With two (2, 3) arrays, axis=0 and axis=1 both yield (2, 2, 3), but the positions have different meanings. Verify where an original array appears.
a = np.ones((2, 3))
b = np.zeros((2, 3))
by_block = np.stack([a, b], axis=0)
by_row = np.stack([a, b], axis=1)
print("Block-first:")
print(by_block)
print("Row-first:")
print(by_row)
np.testing.assert_array_equal(by_block[0], a)
np.testing.assert_array_equal(by_row[:, 0, :], a)
assert not np.array_equal(by_block, by_row)
expect_error('Unequal stack shapes', lambda: np.stack([a, np.zeros((1, 3))]))Expected output
Block-first:
[[[1. 1. 1.]
[1. 1. 1.]]
[[0. 0. 0.]
[0. 0. 0.]]]
Row-first:
[[[1. 1. 1.]
[0. 0. 0.]]
[[1. 1. 1.]
[0. 0. 0.]]]
Unequal stack shapes: ValueErrorFor 2D arrays, (m, k) @ (k, n) produces (m, n). A vector of length k on the right produces shape (m,). Transposing or padding arbitrary data just to avoid the error can change the calculation. Use @ for matrix multiplication; * is elementwise. Higher-dimensional dot and matmul do not have identical rules.
a = np.arange(6).reshape(2, 3)
b = np.arange(9).reshape(3, 3)
expect_error('Vector has the wrong length', lambda: a @ np.array([1, 2]))
v = np.array([1, 2, 3])
print("Matrix result shape:", (a @ b).shape)
print("Vector result:", a @ v)
np.testing.assert_array_equal(a @ v, [8, 26])Expected output
Vector has the wrong length: ValueError
Matrix result shape: (2, 3)
Vector result: [ 8 26]None, newaxis and expand_dims insert a length-one axis. squeeze removes length-one axes; specifying axis prevents unrelated singleton axes from disappearing. Removing a non-singleton axis is invalid.
x = np.arange(6)
column = x[:, None]
row = x[None, :]
print("Column / row:", column.shape, row.shape)
np.testing.assert_array_equal(column, np.expand_dims(x, axis=1))
print("Flat again:", np.squeeze(column, axis=1).shape)
expect_error('Cannot squeeze six elements', lambda: np.squeeze(column, axis=0))
one = np.array([[7]])
print("All axes removed:", np.squeeze(one).shape)
print("One axis retained:", np.squeeze(one, axis=1).shape)Expected output
Column / row: (6, 1) (1, 6)
Flat again: (6,)
Cannot squeeze six elements: ValueError
All axes removed: ()
One axis retained: (1,)Transpose swaps axes of a 2D array but does not turn a 1D vector into a column. A transposed array can be non-contiguous. Request contiguous storage only when needed; it is a storage adjustment, not a repair for the wrong axis meaning. See views and strides.
m = np.arange(12).reshape(3, 4)
mt = m.T
contiguous = np.ascontiguousarray(mt)
print("Original / transpose:", m.shape, mt.shape)
print("Transpose C contiguous:", mt.flags.c_contiguous)
print("Adjusted C contiguous:", contiguous.flags.c_contiguous)
vector = np.arange(3)
print("1D transpose:", vector.T.shape)
np.testing.assert_array_equal(mt, contiguous)
assert vector.T.shape == (3,)Expected output
Original / transpose: (3, 4) (4, 3)
Transpose C contiguous: False
Adjusted C contiguous: True
1D transpose: (3,)Here axes mean depth, row and column. A (4,) vector offsets columns across all depths and rows. A depth offset must be shaped (2, 1, 1). Missing leading dimensions are treated as length one during broadcasting.
t = np.arange(24).reshape(2, 3, 4)
bias = np.array([1, 2, 3, 4])
depth_bias = np.array([10, 20])[:, None, None]
print("Column offset shape:", (t + bias).shape)
print("Depth offset shape:", (t + depth_bias).shape)
np.testing.assert_array_equal((t + depth_bias)[0] - t[0], np.full((3, 4), 10))
np.testing.assert_array_equal((t + depth_bias)[1] - t[1], np.full((3, 4), 20))Expected output
Column offset shape: (2, 3, 4)
Depth offset shape: (2, 3, 4)A mask used to select rows must match the row count. Boolean indexing does not use arithmetic broadcasting to stretch a short mask. Choose the intended dimension before making a condition. See indexing and slicing.
a = np.arange(12).reshape(3, 4)
bad = np.array([True, False])
expect_error('Wrong row mask length', lambda: a[bad], IndexError)
row_mask = a[:, 0] >= 4
selected = a[row_mask]
print("Selected rows:")
print(selected)
assert selected.shape == (2, 4)Expected output
Wrong row mask length: IndexError
Selected rows:
[[ 4 5 6 7]
[ 8 9 10 11]]This synthetic table has days down rows and Books, Pens and Gifts across columns. Multiply each product by its price and sum across products for daily revenue. Check prices, row counts and known totals; compatible shapes alone cannot verify that product order matches.
sales = np.array([[2, 3, 1], [0, 4, 2], [1, 0, 3], [3, 2, 0]])
products = ['Books', 'Pens', 'Gifts']
prices = np.array([10, 2, 5])
assert sales.ndim == 2
assert prices.shape == (sales.shape[1],)
assert len(products) == sales.shape[1]
revenue = sales * prices
daily = revenue.sum(axis=1)
print("Sales / prices / revenue:", sales.shape, prices.shape, revenue.shape)
print("Daily revenue:", daily)
np.testing.assert_array_equal(daily, [31, 18, 25, 34])
assert daily.sum() == 108Expected output
Sales / prices / revenue: (4, 3) (3,) (4, 3)
Daily revenue: [31 18 25 34]Normalizing a sales row needs totals shaped (days, 1). A day with no units has no defined product share. Initialize the output with NaN and divide only where the denominator is positive. See sum() for totals.
units = np.array([[2, 3, 1], [0, 0, 0], [1, 0, 3]], dtype=float)
totals = units.sum(axis=1, keepdims=True)
shares = np.full(units.shape, np.nan)
np.divide(units, totals, out=shares, where=totals > 0)
print("Total shape:", totals.shape)
print("Product shares:")
print(np.round(shares, 3))
np.testing.assert_allclose(shares[[0, 2]].sum(axis=1), 1)
assert np.isnan(shares[1]).all()Expected output
Total shape: (3, 1)
Product shares:
[[0.333 0.5 0.167]
[ nan nan nan]
[0.25 0. 0.75 ]]Cannot reshape: compare size with the target dimension product. Cannot broadcast: compare trailing dimensions and add the singleton axis matching the intended meaning. Axis out of bounds: inspect ndim after slicing. Join dimension mismatch: check rank and every non-join dimension. stack shape mismatch: all input shapes must match. matmul dimension mismatch: match inner dimensions. Boolean index mismatch: check mask length or full mask shape. Print intermediate shapes and assert meaningful expected values. Large broadcast outputs can require substantial memory even when the inputs are small.
1. Add [1, 2, 3] to every row of a (5, 3) array. 2. Join three (10, 4) blocks vertically and calculate column means. 3. Add a column vector [10, 20, 30] to a (3, 5) array. 4. Reshape 24 values into (2, 3, 4), then subtract [1, 2, 3, 4] along the last axis. Predict each result shape before running the solution.
The (3,) vector already aligns with the three columns; no additional dimension is necessary.
a = np.arange(15).reshape(5, 3)
row = np.array([1, 2, 3])
result = a + row
print(result)
assert result.shape == (5, 3)
np.testing.assert_array_equal(result[0], [1, 3, 5])Expected output
[[ 1 3 5]
[ 4 6 8]
[ 7 9 11]
[10 12 14]
[13 15 17]]Join along axis zero to add rows. Mean along axis zero leaves one mean per column.
blocks = [np.full((10, 4), i) for i in range(3)]
big = np.concatenate(blocks, axis=0)
means = big.mean(axis=0)
print("Joined shape:", big.shape)
print("Column means:", means)
assert big.shape == (30, 4)
np.testing.assert_array_equal(means, [1, 1, 1, 1])Expected output
Joined shape: (30, 4)
Column means: [1. 1. 1. 1.]Insert the column axis so a (3, 1) vector expands across five columns.
column = np.array([10, 20, 30])[:, None]
b = np.arange(15).reshape(3, 5)
result = b + column
print(result)
assert result.shape == (3, 5)
np.testing.assert_array_equal(result[:, 0], [10, 25, 40])Expected output
[[10 11 12 13 14]
[25 26 27 28 29]
[40 41 42 43 44]]The (4,) bias applies to columns in every row and depth.
t = np.arange(24).reshape(2, 3, 4)
bias = np.array([1, 2, 3, 4])
result = t - bias
print(result)
assert result.shape == (2, 3, 4)
np.testing.assert_array_equal(result[0, 0], [-1, -1, -1, -1])
np.testing.assert_array_equal(result[1, 2], [19, 19, 19, 19])Expected output
[[[-1 -1 -1 -1]
[ 3 3 3 3]
[ 7 7 7 7]]
[[11 11 11 11]
[15 15 15 15]
[19 19 19 19]]]Open in Google Colab View on GitHub
All sample data is embedded. Run the examples and practise diagnosing and repairing shape errors. Save a copy in Drive to keep your changes.
Continue with NumPy tutorials, shape, broadcasting and reshape(). References: NumPy broadcasting guide, reshape documentation and matmul documentation.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.