Per-Row with reshape(-1, 1)
Rule 3 — 'the tricky one': apply one value per ROW by turning a 1D array into a column vector with reshape(-1, 1) or np.newaxis.
Now flip the problem: each player has a form rating (5 values), and you want to scale every match in that player's row by his own rating. Virat 1.15, Rohit 1.0, Hardik 0.95, Shubman 1.05, Shreyas 1.1. This is the broadcasting case that confuses almost every beginner — Munna calls it "the tricky one".
Definition
A column vector is a 2D array of shape (n, 1) — n rows, one column. Combining a column vector with a 2D array of shape (n, cols) stretches the single column across every column, so each value is applied to one whole row.
- (n, 1) vs (n,) — they hold the same n numbers, but
(n,)is 1D and aligns with the last dimension, while(n, 1)is 2D and aligns its n with the rows. - reshape(-1, 1) — "make it one column, and work out (-1) the number of rows yourself".
- One value per row — in the squad table, rows are players, so this gives a per-player adjustment.
Why the plain 1D array fails
python
import numpy as np
squad_scores = np.array([
[72, 45, 88, 31, 64, 0], # Virat
[56, 0, 74, 88, 32, 45], # Rohit
[45, 32, 0, 67, 18, 54], # Hardik
[33, 61, 45, 0, 77, 83], # Shubman
[88, 14, 57, 72, 0, 38], # Shreyas
])
player_form_rating = np.array([1.15, 1.0, 0.95, 1.05, 1.1])
squad_scores * player_form_ratingtext
ValueError: operands could not be broadcast together with shapes (5,6) (5,)text
squad_scores 5 6
player_form_rating 5 -> (1, 5)
compare right-to-left: 6 vs 5 -> FAILThe 5 matches the rows, but NumPy never looks there first — it always aligns from the right.
Syntax — three ways to make a column vector
python
col = arr.reshape(-1, 1) # most common
col = arr[:, np.newaxis] # insert a new axis of size 1 at position 1
col = arr[:, None] # same thing (np.newaxis is None)
col = np.expand_dims(arr, axis=1) # function form| Expression | Meaning | Result shape for 5 values |
|---|---|---|
reshape(-1, 1) | -1 = infer rows, 1 = exactly one column | (5, 1) |
[:, np.newaxis] | keep all elements, add a new axis after them | (5, 1) |
np.expand_dims(a, axis) | axis: where to insert the new size-1 axis | (5, 1) for axis=1 |
reshape(1, -1) / [np.newaxis, :] | the opposite: a row vector | (1, 5) |
Example — the fix: per-player form rating
python
col = player_form_rating.reshape(-1, 1)
print(col.shape)
print(col)
print((squad_scores * col).round(1))text
(5, 1)
[[1.15]
[1. ]
[0.95]
[1.05]
[1.1 ]]
[[ 82.8 51.7 101.2 35.6 73.6 0. ]
[ 56. 0. 74. 88. 32. 45. ]
[ 42.8 30.4 0. 63.6 17.1 51.3]
[ 34.6 64. 47.2 0. 80.9 87.2]
[ 96.8 15.4 62.7 79.2 0. 41.8]]How it works:
text
squad_scores 5 6
col 5 1
6 vs 1 -> stretch the column across 6 matches
5 vs 5 -> equal
Result: (5, 6)Virat's whole row is multiplied by 1.15 (72 → 82.8), Rohit's row by 1.0 (unchanged), Hardik's by 0.95 and so on.
Example — compare each score with the player's own average
Aggregating with axis=1 gives one value per row, shape (5,) — which does not broadcast back. Reshape it, or use keepdims=True:
python
player_avg = squad_scores.mean(axis=1)
print(player_avg.round(2))
centered = squad_scores - player_avg.reshape(-1, 1)
print(centered.round(1))
print(squad_scores.mean(axis=1, keepdims=True).shape)text
[50. 49.17 36. 49.83 44.83]
[[ 22. -5. 38. -19. 14. -50. ]
[ 6.8 -49.2 24.8 38.8 -17.2 -4.2]
[ 9. -4. -36. 31. -18. 18. ]
[-16.8 11.2 -4.8 -49.8 27.2 33.2]
[ 43.2 -30.8 12.2 27.2 -44.8 -6.8]]
(5, 1)How it works: keepdims=True keeps the reduced axis with size 1, so the result is already (5, 1) — ready to broadcast. squad_scores - squad_scores.mean(axis=1, keepdims=True) is the cleanest one-liner.
Example — column + row = a full table
Combining a column vector with a row (1D) array makes both stretch — a quick way to build a table of every combination. Runs scored after 1–5 overs at run rates 6, 8 and 10:
python
overs = np.arange(1, 6) # (5,)
rr = np.array([6, 8, 10]) # (3,)
print(overs.reshape(-1, 1) * rr)text
[[ 6 8 10]
[12 16 20]
[18 24 30]
[24 32 40]
[30 40 50]](5, 1) × (3,) → (5, 3): row = overs, column = run rate.
Mental shortcut
| You want one value per… | Shape of the small array | How |
|---|---|---|
| element (everything) | scalar () | arr + 10 |
| column (match) | (cols,) | plain 1D array |
| row (player) | (rows, 1) | .reshape(-1, 1) or [:, np.newaxis] |
Common mistakes
- ❌
scores * ratingswithratings.shape == (rows,). ✅scores * ratings.reshape(-1, 1). - ❌
reshape(1, -1)when you wanted a column. ✅(1, -1)is a row;(-1, 1)is a column. - ❌
scores - scores.mean(axis=1). ✅scores - scores.mean(axis=1, keepdims=True).
Interview tip
Q: What is the difference between shapes (5,), (5, 1) and (1, 5)?
A: (5,) is a 1D array — it aligns with the last dimension. (5, 1) is a column vector (5 rows, 1 column) — it broadcasts across columns, one value per row. (1, 5) is a row vector — it broadcasts down rows, one value per column, behaving like (5,).
Key points to remember
- A plain 1D array of length = number of ROWS does not broadcast with a 2D array (unless it happens to equal the column count).
- Turn it into a column vector of shape (rows, 1) with reshape(-1, 1), [:, np.newaxis], [:, None] or np.expand_dims(a, 1).
- A (rows, 1) column stretches across every column, so each value applies to one whole row (one player).
- reshape(-1, 1) is a column vector; reshape(1, -1) is a row vector.
- mean(axis=1, keepdims=True) keeps shape (rows, 1), so it broadcasts back without reshaping.
- Column (n, 1) combined with row (m,) produces a full (n, m) table of every combination.
Try it yourself
The failure, then the reshape(-1, 1) fix
Four ways to make a column vector
Each score vs the player's own average (keepdims)
Practice: per-player fitness bonus
Revision Flashcards
Tap a card to flip it and see the answer.
