Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

NumPy & SciPy for Scientific Computing  Module 2 – Indexing, reshaping and vectorised operations

Reshaping, stacking and splitting arrays

Reshape NumPy arrays with -1 in C or F order, move axes, join with concatenate and stack, split them back, and replace the calls NumPy 2.5 deprecates.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8, numpy 2.5.3, scipy 1.18.1 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Predict where each value lands when you reshape an array with -1 in C or F order
  • Move, add and remove axes with moveaxis, transpose, swapaxes, expand_dims and squeeze
  • Join arrays with concatenate, stack, vstack, hstack, column_stack and np.block, and split them back
  • Choose between tile, repeat and broadcast_to by what each repeats and the memory it takes
  • Replace shape assignment, np.row_stack and in-place resize with the calls NumPy 2.5 supports

Before you start

What you’ll learn · Before you start

What you will learn

  • Predict where each value lands when you reshape an array with -1 in C or F order
  • Move, add and remove axes with moveaxis, transpose, swapaxes, expand_dims and squeeze
  • Join arrays with concatenate, stack, vstack, hstack, column_stack and np.block, and split them back
  • Choose between tile, repeat and broadcast_to by what each repeats and the memory it takes
  • Replace shape assignment, np.row_stack and in-place resize with the calls NumPy 2.5 supports

Before you start

On this page

Three years of monthly electricity use arrive as one row of 36 numbers, and the quarterly totals are a single line away: kwh.reshape(3, 4, 3).sum(axis=2). Reshaping reads the values in one order and lays them into a new shape without moving them, which is why it is instant, and also why reading data in the wrong order raises no error.

You will reshape with -1 in C or F order, move, add and remove axes, join arrays with concatenate, stack and their shortcuts, split them back, repeat values with or without copying, and replace the calls that NumPy 2.5 deprecates or removes.

reshape keeps the order of the values and works out one length

Here are the 36 readings, January to December of three years, read as years and then as quarters:

36 monthly readings as years, quarters and months Python · reshape/monthly.py
"""Three years of monthly electricity use: one row of 36 numbers read as years, quarters and months."""
import numpy as np

kwh = np.array([
    310, 280, 260, 230, 250, 340, 380, 370, 300, 260, 270, 320,  # year 1, January to December
    330, 290, 270, 240, 260, 350, 400, 390, 310, 270, 280, 340,  # year 2
    320, 300, 280, 250, 270, 360, 410, 400, 320, 280, 290, 350,  # year 3
], dtype=np.int64)
years = kwh.reshape(3, 12)  # the same 36 values, read as 3 rows of 12
print("years:", years.shape, "| a view of kwh:", np.shares_memory(years, kwh))
quarters = years.reshape(3, 4, 3)  # each year as 4 quarters of 3 months
print("quarterly totals:\n", quarters.sum(axis=2))
print("yearly totals:", years.sum(axis=1))
print("reshape(-1, 12):", kwh.reshape(-1, 12).shape, "| reshape(6, -1):", kwh.reshape(6, -1).shape)
for shape in [(5, -1), (-1, -1)]:
    try:
        kwh.reshape(shape)
    except ValueError as error:
        print(f"reshape{shape}: ValueError: {error}")
six = np.arange(6, dtype=np.int64)
print("order C:", six.reshape(2, 3).tolist(), "| order F:", six.reshape(2, 3, order="F").tolist())
print("F order read back in F order:", six.reshape(2, 3, order="F").reshape(-1, order="F").tolist())

Output

years: (3, 12) | a view of kwh: True
quarterly totals:
 [[ 850  820 1050  850]
 [ 890  850 1100  890]
 [ 900  880 1130  920]]
yearly totals: [3570 3730 3830]
reshape(-1, 12): (3, 12) | reshape(6, -1): (6, 6)
reshape(5, -1): ValueError: cannot reshape array of size 36 into shape (5,newaxis)
reshape(-1, -1): ValueError: can only specify one unknown dimension
order C: [[0, 1, 2], [3, 4, 5]] | order F: [[0, 2, 4], [1, 3, 5]]
F order read back in F order: [0, 1, 2, 3, 4, 5]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 monthly.py

kwh.reshape(3, 12) lays the values into 3 rows of 12, one year a row, and it is a view: no value moved, as np.shares_memory shows. years.reshape(3, 4, 3) reads each year as 4 quarters of 3 months, so summing the last axis gives the quarterly totals, 850, 820, 1,050 and 850 kWh in year 1. One length may be -1, which reshape works out from the size: (-1, 12) became (3, 12) and (6, -1) became (6, 6). A size that does not divide, as in (5, -1), and two unknown lengths both raise a ValueError.

The order is the one thing reshape must be told. In the default C order: Row-major layout: the last index changes fastest in memory, so the values of one row sit side by side. NumPy makes new arrays in C order unless asked otherwise. More in “The ndarray: shape, dtype, strides and memory” the last index changes fastest; with order="F", the F order: Column-major (Fortran) layout: the first index changes fastest in memory, so the values of one column sit side by side. A transpose of a C-order array is in F order. More in “The ndarray: shape, dtype, strides and memory” of Fortran and MATLAB, the first index does. Step through six values in each order:

Interactive · step by step Six values into two rows, in C order and in F order Step through the two orders. The left column holds the values in their flat order; the right column shows the rows of the (2, 3) result.
Six values into two rows, in C order and in F order, step 6 of 6. Showing 0 1 2 3 4 5, the order (F: first index fastest), row 0 (0 2 4), row 1 (1 3 5).0 1 2 3 4 5flat orderthe orderF: first index fastestrow 00 2 4row 11 3 5

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Six values in a row. reshape reads the values in one order and writes them into the new shape in the same order. It never sorts or moves a value out of turn.
  2. C order: rows first. In C order, the default, the last index changes fastest, so the first three values fill row 0 from left to right.
  3. C order: the next row. Then row 1 takes 3, 4 and 5. Flat position k lands at row k // 3, column k % 3.
  4. F order: columns first. With order="F" the first index changes fastest, so the values fill column 0 top to bottom first: 0 in row 0, 1 in row 1.
  5. F order: every column. Then column 1 takes 2 and 3, and column 2 takes 4 and 5: the rows read 0 2 4 and 1 3 5, as the run printed.
  6. Read it back the same way. Reading this result back in F order gives 0 to 5 again; reading it in C order gives 0 2 4 1 3 5. Data written in one order must be read in that order.

np.arange(6).reshape(2, 3, order="F") gave [[0, 2, 4], [1, 3, 5]], and reading it back in F order gave 0 to 5 again. NumPy’s reference adds that C and F here “only refer to the order of indexing”, not to how the array happens to sit in memory.

Reading column-major data in row-major order

A matrix that MATLAB or Fortran code wrote column by column, reshaped with the default C order, comes out with its values in the wrong places and no error. Pass order="F" for data written that way.

Checkpoint

Check: reshape, -1 and memory order

  1. Question 1 of 2 kwh holds 36 values. What shape does kwh.reshape(-1, 4) have?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: (9, 4)

    reshape allows one -1, which it replaces with whatever length makes the sizes match: 36 = 9 × 4.

  2. Question 2 of 2 What is np.arange(6).reshape(2, 3, order="F")?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: [[0, 2, 4], [1, 3, 5]]

    order="F" reads and writes column by column, the layout of Fortran and MATLAB, as the run printed.

Move, add and remove axes without copying

Files and most image code store a picture as (height, width, channels), while many deep-learning libraries expect (channels, height, width). Moving an axis is a view, and only the strides: The number of bytes to move in memory to reach the next element along each axis. A (3, 4) int64 array in C order has strides (32, 8). More in “The ndarray: shape, dtype, strides and memory” change. Here is a tiny RGB image, 4 pixels high and 6 wide:

An RGB image moved to channels-first, with axes added and removed Python · axes/channels.py
"""A tiny RGB image moved to channels-first, and axes of length 1 added and removed."""
import numpy as np

img = np.arange(4 * 6 * 3, dtype=np.uint8).reshape(4, 6, 3)  # height 4, width 6, 3 colour channels
chw = np.moveaxis(img, -1, 0)  # channels first, the layout many deep-learning libraries expect
print("img", img.shape, "strides", img.strides, "| moveaxis(img, -1, 0)", chw.shape, "strides", chw.strides)
print("moveaxis gives a view:", np.shares_memory(img, chw))
print("np.transpose(img, (2, 0, 1)):", np.transpose(img, (2, 0, 1)).shape, "| np.swapaxes(img, 0, 2):", np.swapaxes(img, 0, 2).shape)
red = img[:, :, 0]
print("red:", red.shape, "| np.expand_dims(red, -1):", np.expand_dims(red, -1).shape)
batch = np.expand_dims(chw, 0)  # a batch that holds one image
print("batch:", batch.shape, "| np.squeeze(batch):", np.squeeze(batch).shape, "| np.squeeze(batch, axis=0):", np.squeeze(batch, axis=0).shape)
try:
    np.squeeze(batch, axis=1)
except ValueError as error:
    print("np.squeeze(batch, axis=1): ValueError:", error)

Output

img (4, 6, 3) strides (18, 3, 1) | moveaxis(img, -1, 0) (3, 4, 6) strides (1, 18, 3)
moveaxis gives a view: True
np.transpose(img, (2, 0, 1)): (3, 4, 6) | np.swapaxes(img, 0, 2): (3, 6, 4)
red: (4, 6) | np.expand_dims(red, -1): (4, 6, 1)
batch: (1, 3, 4, 6) | np.squeeze(batch): (3, 4, 6) | np.squeeze(batch, axis=0): (3, 4, 6)
np.squeeze(batch, axis=1): ValueError: cannot select an axis to squeeze out which has size not equal to one

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 channels.py

np.moveaxis(img, -1, 0) moved the channel axis to the front, shape (3, 4, 6), with strides (1, 18, 3): the same bytes read in a new order. NumPy’s reference says of it that the “other axes remain in their original order”. np.transpose(img, (2, 0, 1)) takes the full new order of axes and gives the same result, while np.swapaxes(img, 0, 2) exchanges two axes and gives (3, 6, 4), with height and width swapped as well.

np.expand_dims adds an axis of length 1, as np.newaxis does: the red channel (4, 6) became (4, 6, 1), and one image became a batch of one, (1, 3, 4, 6). np.squeeze removes axes of length 1, and when you name the axis it refuses one that is not of length 1, as the ValueError for axis 1 showed.

Name the axis you squeeze

np.squeeze(batch) removes every axis of length 1, so a batch of one image with one channel would lose both. np.squeeze(batch, axis=0) removes the batch axis only, and raises if it is not of length 1.

Checkpoint

Check: Moving, adding and removing axes

  1. Question 1 of 2 img has shape (4, 6, 3). What shape does np.moveaxis(img, -1, 0) have, and is it a copy?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: (3, 4, 6), and it is a view

    moveaxis, transpose and swapaxes only rearrange strides, so a channels-first image costs nothing to make.

  2. Question 2 of 2 batch has shape (1, 3, 4, 6). What does np.squeeze(batch, axis=1) do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It raises ValueError, because axis 1 has length 3

    Name the axis to squeeze when you know it: an error is better than squeezing an axis that happens to be 1.

concatenate joins along an axis, stack adds one

Joining makes a new array and copies the inputs into it. Two functions do the work and the others are shortcuts: np.concatenate makes an axis the inputs already have longer, and np.stack joins them, in the words of its reference, “along a new axis”. The run joins two vectors of three values in every way:

Two vectors joined six ways, and a block matrix Python · join/joining.py
"""Joining arrays: concatenate along an axis that exists, stack along a new one, and the shortcuts."""
import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
for label, joined in [
    ("np.concatenate([a, b])", np.concatenate([a, b])),
    ("np.stack([a, b])", np.stack([a, b])),
    ("np.stack([a, b], axis=1)", np.stack([a, b], axis=1)),
    ("np.vstack([a, b])", np.vstack([a, b])),
    ("np.hstack([a, b])", np.hstack([a, b])),
    ("np.column_stack([a, b])", np.column_stack([a, b])),
]:
    print(f"{label:<25} {str(joined.shape):<7} {joined.tolist()}")
m = np.ones((2, 3), dtype=np.int64)
print("2-D, axis=0:", np.concatenate([m, 2 * m]).shape, "| axis=1:", np.concatenate([m, 2 * m], axis=1).shape)
try:
    np.concatenate([a, m])
except ValueError as error:
    print("np.concatenate([a, m]): ValueError:", error)
block = np.block([[m, np.zeros((2, 1), dtype=np.int64)], [np.zeros((1, 3), dtype=np.int64), np.ones((1, 1), dtype=np.int64)]])
print("np.block:", block.shape)
print(block)
print("a join copies its parts:", not np.shares_memory(np.concatenate([a, b]), a))

Output

np.concatenate([a, b])    (6,)    [1, 2, 3, 4, 5, 6]
np.stack([a, b])          (2, 3)  [[1, 2, 3], [4, 5, 6]]
np.stack([a, b], axis=1)  (3, 2)  [[1, 4], [2, 5], [3, 6]]
np.vstack([a, b])         (2, 3)  [[1, 2, 3], [4, 5, 6]]
np.hstack([a, b])         (6,)    [1, 2, 3, 4, 5, 6]
np.column_stack([a, b])   (3, 2)  [[1, 4], [2, 5], [3, 6]]
2-D, axis=0: (4, 3) | axis=1: (2, 6)
np.concatenate([a, m]): ValueError: all the input arrays must have same number of dimensions, but the array at index 0 has 1 dimension(s) and the array at index 1 has 2 dimension(s)
np.block: (3, 4)
[[1 1 1 0]
 [1 1 1 0]
 [0 0 0 1]]
a join copies its parts: True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 joining.py

concatenate gave one vector of 6 values. stack gave (2, 3), one row for each input, or (3, 2) with axis=1. vstack turns 1-D inputs into rows before joining them along the first axis, column_stack turns them into columns, and hstack joins 1-D inputs end to end, like concatenate. concatenate needs inputs with the same number of axes, so a (3,) vector and a (2, 3) table failed. np.block builds an array from nested lists of blocks, joining each inner list side by side and then the rows of blocks, here a (3, 4) array from four blocks. None of the results shares memory with its parts.

Three questions about your arrays decide which function to use. Answer them in the table:

Interactive · answer and compare Which function joins your arrays the way you need? Answer three questions about the arrays you are joining. The table marks the functions that fit and says why the others do not.
Do your arrays already have the axis you want to join along?
Are you joining 1-D vectors, and should each become a row or a column?
Are you building one array from a grid of blocks, such as a block matrix?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Six ways to join arrays against the three things that decide between them
Function Two (3,) vectors give Adds an axis Inputs must
np.concatenate Joins along: an axis the inputs already have Mixed (6,): one longer vector Mixed never Mixed have the same number of axes and match off the joined axis
np.stack Joins along: a new axis, placed by axis= Good (2, 3) as rows, or (3, 2) with axis=1 Good always one Mixed all have the same shape
np.vstack Joins along: the first axis, after 1-D becomes a row Good (2, 3): one row each Mixed only for 1-D input Good match in every axis but the first
np.hstack Joins along: the second axis, or the only one of 1-D input Mixed (6,), like concatenate Mixed never Good match in every axis but the second
np.column_stack Joins along: the second axis, after 1-D becomes a column Good (3, 2): one column each Mixed only for 1-D input Good have the same length; 2-D ones join as hstack does
np.block Joins along: the last axis, then the ones before it Mixed np.block([[a], [b]]) gives (2, 3) Mixed no; nesting sets the layout Good fit together as a grid of blocks
How the answers decide
  • “Do your arrays already have the axis you want to join along?” Yes, make that axis longer: rules out np.stack. np.stack always adds an axis; to make an existing axis longer, concatenate along it (hstack and vstack are shortcuts).
  • “Do your arrays already have the axis you want to join along?” No, the result needs a new axis: rules out np.concatenate and np.hstack. concatenate and hstack never add an axis: two (3,) vectors become one (6,) vector.
  • “Are you joining 1-D vectors, and should each become a row or a column?” Vectors, each one a row: rules out np.column_stack, np.concatenate and np.hstack and adds a caveat to np.block. For rows, np.vstack or np.stack gives (2, 3). concatenate and hstack join vectors end to end into (6,), column_stack makes columns, and block needs each vector wrapped in a list.
  • “Are you joining 1-D vectors, and should each become a row or a column?” Vectors, each one a column: rules out np.vstack, np.concatenate and np.hstack and adds a caveat to np.stack and np.block. For columns, np.column_stack gives (3, 2), and np.stack does too with axis=1. vstack makes rows, concatenate and hstack join vectors end to end, and block needs columns made first.
  • “Are you building one array from a grid of blocks, such as a block matrix?” Yes: adds a caveat to np.concatenate, np.stack, np.vstack, np.hstack and np.column_stack. A grid of blocks takes several joins by hand; np.block builds it in one call from nested lists, rows of blocks inside.

In short: concatenate to make an axis longer, stack to add one, vstack or column_stack to turn vectors into rows or columns, and np.block for a grid of blocks.

Checkpoint

Check: concatenate, stack and the shortcuts

  1. Question 1 of 2 a and b both have shape (3,). Which call gives shape (2, 3)?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: np.stack([a, b])

    np.vstack([a, b]) gives (2, 3) as well, by turning each vector into a row first.

  2. Question 2 of 2 Why does np.concatenate([a, m]) fail for a of shape (3,) and m of shape (2, 3)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: concatenate needs inputs with the same number of axes; a has 1 and m has 2

    Give a the missing axis, a[np.newaxis] or a.reshape(1, 3), or let vstack do it.

Splitting cuts an array back into views

Splitting undoes a join, and it copies nothing: each part is a slice of the original. The run cuts the image into its colour channels, cuts a vector at chosen positions, and cuts one into unequal parts:

Equal parts, parts at chosen positions and unequal parts Python · split/splitting.py
"""Splitting undoes a join: equal parts with split, unequal ones with array_split, or at chosen positions."""
import numpy as np

img = np.arange(4 * 6 * 3, dtype=np.uint8).reshape(4, 6, 3)
r, g, b = np.split(img, 3, axis=2)  # three (4, 6, 1) pieces, one per colour
print("np.split(img, 3, axis=2):", r.shape, g.shape, b.shape, "| views of img:", np.shares_memory(r, img))
print("concatenated back, equal to img:", np.array_equal(np.concatenate([r, g, b], axis=2), img))
x = np.arange(10)
print("np.split(x, [3, 7]):", [part.tolist() for part in np.split(x, [3, 7])])
print("np.array_split(np.arange(7), 3):", [part.tolist() for part in np.array_split(np.arange(7), 3)])
try:
    np.split(np.arange(7), 3)
except ValueError as error:
    print("np.split(np.arange(7), 3): ValueError:", error)

Output

np.split(img, 3, axis=2): (4, 6, 1) (4, 6, 1) (4, 6, 1) | views of img: True
concatenated back, equal to img: True
np.split(x, [3, 7]): [[0, 1, 2], [3, 4, 5, 6], [7, 8, 9]]
np.array_split(np.arange(7), 3): [[0, 1, 2], [3, 4], [5, 6]]
np.split(np.arange(7), 3): ValueError: array split does not result in an equal division

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 splitting.py

np.split(img, 3, axis=2) gave three (4, 6, 1) pieces, one per colour, each a view: An array that reads another array's memory instead of holding its own copy, such as a slice or a transpose. A change made through a view changes the original. More in “The ndarray: shape, dtype, strides and memory” of img, and concatenating them back gave an array equal to img. A list of positions says where to cut: [3, 7] gives x[:3], x[3:7] and x[7:]. A number of sections asks for equal parts, and 7 values do not make 3 of them, hence the ValueError. np.array_split accepts any count: NumPy’s reference says it makes n % sections parts of size n // sections + 1 and the rest of size n // sections, so 7 values became parts of 3, 2 and 2. np.vsplit, np.hsplit and np.dsplit are split along axes 0, 1 and 2, except that hsplit cuts a 1-D array along its only axis.

Checkpoint

Check: split and array_split

  1. Question 1 of 2 What sizes do the parts of np.array_split(np.arange(10), 4) have?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 3, 3, 2 and 2

    array_split is split with the rule for uneven sizes that NumPy's reference states: n % sections larger parts first.

  2. Question 2 of 2 Are the pieces of np.split(img, 3, axis=2) copies of the image?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No, they are views of img

    Writing to a split piece changes the original image; concatenating the pieces back makes a new array.

Repeating values: tile, repeat or a read-only view

Three functions repeat values, and they differ in what they repeat and in the memory they take:

tile, repeat and broadcast_to, and the memory each takes Python · repeat/repeating.py
"""tile repeats the whole array, repeat repeats each element, and broadcast_to repeats without copying."""
import numpy as np

t = np.array([1, 2, 3], dtype=np.int64)
print("np.tile(t, 2):", np.tile(t, 2).tolist(), "| np.repeat(t, 2):", np.repeat(t, 2).tolist())
print("np.tile(t, (2, 1)):", np.tile(t, (2, 1)).tolist())
print("np.repeat(t, [3, 0, 1]):", np.repeat(t, [3, 0, 1]).tolist())
tiled = np.tile(t, (1000, 1))
view = np.broadcast_to(t, (1000, 3))
print("np.tile(t, (1000, 1)):", tiled.shape, "with", tiled.nbytes, "bytes of its own")
print("np.broadcast_to(t, (1000, 3)):", view.shape, "strides", view.strides, "| reads t's 24 bytes:", np.shares_memory(view, t))
print("its nbytes still says", view.nbytes, "| writeable:", view.flags.writeable)
try:
    view[0, 0] = 9
except ValueError as error:
    print("view[0, 0] = 9: ValueError:", error)

Output

np.tile(t, 2): [1, 2, 3, 1, 2, 3] | np.repeat(t, 2): [1, 1, 2, 2, 3, 3]
np.tile(t, (2, 1)): [[1, 2, 3], [1, 2, 3]]
np.repeat(t, [3, 0, 1]): [1, 1, 1, 3]
np.tile(t, (1000, 1)): (1000, 3) with 24000 bytes of its own
np.broadcast_to(t, (1000, 3)): (1000, 3) strides (0, 8) | reads t's 24 bytes: True
its nbytes still says 24000 | writeable: False
view[0, 0] = 9: ValueError: assignment destination is read-only

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 repeating.py

np.tile repeats the whole array and np.repeat each element, with one count per element if you give a list, and both write every repeat into new memory: np.tile(t, (1000, 1)) owns 24,000 bytes. np.broadcast_to(t, (1000, 3)) has the same shape but a stride of 0 along the new axis, so all 1,000 rows read the same 24 bytes of t. NumPy’s reference calls it “a readonly view on the original array”, a read-only view: A view that NumPy refuses to write through, such as the result of np.broadcast_to, where many positions read the same memory. Its flags.writeable is False, and a write raises ValueError., and writing to it raised ValueError: assignment destination is read-only. Its nbytes still reports 24,000, the positions it covers rather than memory it owns.

Key idea

tile and repeat write every repeat into new memory; broadcast_to shows the repeats through a stride of 0 and is read-only for that reason.

Checkpoint

Check: tile, repeat and broadcast_to

  1. Question 1 of 2 view = np.broadcast_to(t, (1000, 3)). What happens on view[0, 0] = 9?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: ValueError: assignment destination is read-only

    For a writable repeated array, copy it, np.broadcast_to(t, (1000, 3)).copy(), or use np.tile.

  2. Question 2 of 2 What is np.repeat([1, 2, 3], 2)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: [1, 1, 2, 2, 3, 3]

    tile repeats the whole array, repeat each element, and broadcast_to repeats without copying anything.

What NumPy 2.5 deprecates, and what to write instead

Four releases changed the calls of this lesson, each first with a deprecation: A notice that a feature will be removed in a later release. NumPy warns with a DeprecationWarning while the feature still works, then removes it some releases later, as it did with np.row_stack., a warning that the old form will go, and later with its removal. Walk through them in order:

Timeline · 4 events in order How reshaping and stacking changed from NumPy 2.0 to 2.5 Walk through the releases in order. Each one names what changed and what to write instead.
  1. 2.0

    np.row_stack is deprecated deprecated

    np.row_stack, which only ever did what np.vstack does, starts to warn. New code calls np.vstack.

  2. 2.1

    reshape gains shape= and copy= new keywords

    np.reshape and ndarray.reshape accept shape= and copy=, and copy=False raises a ValueError instead of copying. The old keyword newshape= is deprecated.

  3. 2.4

    newshape= is removed removed

    np.reshape no longer takes newshape=: pass the shape by position or as shape=. The release notes also announce that setting arr.shape will be deprecated.

  4. 2.5

    Setting shape and dtype warns deprecated

    Assigning to arr.shape or arr.dtype raises a DeprecationWarning, because changing a shared array in place is unsafe. Resizing in place is deprecated too, and np.row_stack is removed.

The timeline has 4 events, from 2.0 to 2.5.

The run tries each old form on the pinned NumPy 2.5.3 and shows what replaces it:

The deprecated and removed forms on NumPy 2.5.3, with their replacements Python · deprecated/numpy25.py
"""What NumPy 2.5 deprecates or removes around reshaping and stacking, and what replaces each."""
import warnings

import numpy as np

print("NumPy", np.__version__)


def warnings_of(change):
    with warnings.catch_warnings(record=True) as caught:
        warnings.simplefilter("always")
        change()
    return [f"{w.category.__name__}: {str(w.message).splitlines()[0]}" for w in caught]


x = np.arange(6, dtype=np.int64)
print("x.shape = (2, 3):", warnings_of(lambda: setattr(x, "shape", (2, 3))))
print("  instead np.reshape(x, (2, 3)):", np.reshape(np.arange(6), (2, 3)).shape)
y = np.arange(6, dtype=np.int64)
print("y.dtype = np.uint64:", warnings_of(lambda: setattr(y, "dtype", np.uint64)))
print("  instead y.view(np.uint64):", np.arange(6, dtype=np.int64).view(np.uint64).dtype)
z = np.arange(6, dtype=np.int64)
print("z.resize((2, 3)) in place:", warnings_of(lambda: z.resize((2, 3))))
print("  instead np.resize(np.arange(4), (2, 3)):", np.resize(np.arange(4), (2, 3)).tolist())
try:
    np.row_stack
except AttributeError as error:
    print("np.row_stack:", error, "| instead np.vstack")
try:
    np.reshape(np.arange(6), newshape=(2, 3))
except TypeError as error:
    print("np.reshape(..., newshape=...):", error, "| instead shape=")

Output

NumPy 2.5.3
x.shape = (2, 3): ['DeprecationWarning: Setting the shape on a NumPy array has been deprecated in NumPy 2.5.']
  instead np.reshape(x, (2, 3)): (2, 3)
y.dtype = np.uint64: ['DeprecationWarning: Setting the dtype on a NumPy array has been deprecated in NumPy 2.5.']
  instead y.view(np.uint64): uint64
z.resize((2, 3)) in place: []
  instead np.resize(np.arange(4), (2, 3)): [[0, 1, 2], [3, 0, 1]]
np.row_stack: module 'numpy' has no attribute 'row_stack' | instead np.vstack
np.reshape(..., newshape=...): reshape() got an unexpected keyword argument 'newshape' | instead shape=

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 numpy25.py

Assigning to x.shape and to y.dtype both warned with a DeprecationWarning. The release notes give the reason, that changing an array in place is unsafe when it is shared, and the replacements: np.reshape, with copy=False when no copy is allowed, and arr.view(dtype). Both make a new header and leave every other reference to the array as it was. z.resize((2, 3)) changed z in place with no warning at all on 2.5.3, although the 2.5 release notes deprecate resizing in place, so do not wait for a warning to find such calls. Their replacement, np.resize, returns a new array, and when it grows it repeats the values, as [[0, 1, 2], [3, 0, 1]] shows, where the in-place method fills the new places with zeros: its reference points out the difference, so check code that relied on the zeros. np.row_stack is gone, and newshape= raises a TypeError.

The same code on the browser's NumPy 2.4.6

Pyodide in the browser ships NumPy 2.4.6, where np.row_stack still exists (deprecated since 2.0) and setting shape is only announced as a coming deprecation. np.reshape, np.vstack and np.resize behave the same on both versions, so write those.

Checkpoint

Check: NumPy 2.5 deprecations

  1. One question On NumPy 2.5.3, what does x.shape = (2, 3) do, and what should replace it?

    Choose one answer.

    Show the answer: One question

    Answer: It reshapes x but warns with a DeprecationWarning; use x = np.reshape(x, (2, 3))

    reshape returns a view where it can, so replacing the assignment costs nothing, and np.reshape(x, shape, copy=False) keeps the promise that no data is copied.

Practise: follow values through reshape and split

Two functions in plain Python compute what reshape and the split functions decide: the flat position of any index in C or F order and back, and where array_split and split cut. The sample tests run in the page:

Code lab · 2 steps

Follow values through reshape and split

Map flat positions to indexes and back in C and F order, then work out where split and array_split cut an axis.

  1. Map flat positions to indexes in C and F order To do
  2. Work out where split and array_split cut To do

Exercise · Medium · Python

Map flat positions to indexes in C and F order

reshape keeps the order of the values: the value at flat position k lands at the index that position k has in the new shape. Write two functions in positions.py, with Python's own numbers (no NumPy needed):

  • ravel(index, shape, order="C") returns the flat position of the tuple index in an array of that shape. In "C" order the last index changes fastest; in "F" order the first does. An index outside its axis raises ValueError.
  • unravel(k, shape, order="C") does the opposite and returns the index tuple of flat position k. A position below 0 or past the last one raises ValueError.

These are what np.ravel_multi_index and np.unravel_index compute. The lesson's runs give the first checks: in a (3, 12) table of months, flat position 13 is (1, 1), February of year 2; and (2, 3) in F order holds the values 0 2 4 and 1 3 5, so unravel(3, (2, 3), "F") is (1, 1).

Starter code · positions.py

def ravel(index, shape, order="C"):
    """Return the flat position of index in an array of this shape, in C or F order."""
    return sum(index)


def unravel(k, shape, order="C"):
    """Return the index tuple of flat position k in an array of this shape, in C or F order."""
    return tuple(k % n for n in shape)
The sample tests · test_positions.py
from positions import ravel, unravel


def test_the_lessons_months():
    """finds February of year 2 and the last month in a (3, 12) table and in (3, 4, 3)"""
    assert unravel(13, (3, 12)) == (1, 1)
    assert unravel(35, (3, 4, 3)) == (2, 3, 2)
    assert ravel((2, 3, 2), (3, 4, 3)) == 35


def test_f_order():
    """lets the first index change fastest in F order"""
    assert unravel(3, (2, 3), "F") == (1, 1)
    assert unravel(7, (3, 4), "F") == (1, 2)
    assert ravel((1, 2), (3, 4), "F") == 7
    assert ravel((1, 2), (3, 4)) == 6


def test_round_trips():
    """gets back every position of a (2, 3, 4) array in both orders"""
    for order in ("C", "F"):
        assert [ravel(unravel(k, (2, 3, 4), order), (2, 3, 4), order) for k in range(24)] == list(range(24))


def test_out_of_range():
    """refuses positions and indexes that are not in the array"""
    for call in (lambda: unravel(12, (3, 4)), lambda: unravel(-1, (3, 4)), lambda: ravel((3, 0), (3, 4))):
        try:
            call()
        except ValueError:
            continue
        assert False, "an out-of-range position or index must raise ValueError"
A hint

In C order, walk the axes from the last one to the first: k % n is the index on an axis of length n, and k // n is what is left for the axes before it. F order does the same, walking from the first axis to the last. ravel reverses it: multiply by each axis length and add the next index.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Exercise · Easy · Python

Work out where split and array_split cut

Write two functions in bounds.py, with Python's own numbers. Each returns the (start, stop) of every part, in order, for an axis of length n, so that part i is a[start:stop].

  • sections_bounds(n, sections) cuts as np.array_split(a, sections) does: NumPy's reference says it makes n % sections parts of size n // sections + 1 and the rest of size n // sections, the larger parts first. A sections below 1 raises ValueError.
  • positions_bounds(n, positions) cuts as np.split(a, positions) does at a list of increasing positions: the parts run from 0 to the first position, between positions, and from the last position to the end. A position past n leaves an empty part, (n, n).

The lesson's run gives the first checks: sections_bounds(7, 3) is [(0, 3), (3, 5), (5, 7)] and positions_bounds(10, [3, 7]) is [(0, 3), (3, 7), (7, 10)].

Starter code · bounds.py

def sections_bounds(n, sections):
    """Return the (start, stop) of each part that np.array_split(a, sections) makes from n values."""
    size = n // sections
    return [(i * size, (i + 1) * size) for i in range(sections)]


def positions_bounds(n, positions):
    """Return the (start, stop) of each part that np.split(a, positions) makes from n values."""
    return [(0, n)]
The sample tests · test_bounds.py
from bounds import positions_bounds, sections_bounds


def test_the_lessons_splits():
    """cuts where the lesson's run cut"""
    assert sections_bounds(7, 3) == [(0, 3), (3, 5), (5, 7)]
    assert positions_bounds(10, [3, 7]) == [(0, 3), (3, 7), (7, 10)]


def test_larger_parts_come_first():
    """gives the n % sections larger parts first, and equal parts when the sections divide n"""
    assert sections_bounds(10, 4) == [(0, 3), (3, 6), (6, 8), (8, 10)]
    assert sections_bounds(9, 3) == [(0, 3), (3, 6), (6, 9)]
    assert sections_bounds(2, 3) == [(0, 1), (1, 2), (2, 2)]


def test_positions_at_the_edges():
    """leaves an empty part for a position past the end, and one part for no positions"""
    assert positions_bounds(5, [2, 8]) == [(0, 2), (2, 5), (5, 5)]
    assert positions_bounds(5, []) == [(0, 5)]
    assert positions_bounds(5, [0, 5]) == [(0, 0), (0, 5), (5, 5)]


def test_no_sections_is_refused():
    """refuses fewer than one section"""
    try:
        sections_bounds(5, 0)
    except ValueError:
        return
    assert False, "sections = 0 must raise ValueError"
A hint

For sections_bounds, big, rest = divmod(n, sections) gives the base size and how many parts get one value more. Keep a running start, and each part ends at start + big + 1 while there are larger parts left, then at start + big. For positions_bounds, put 0 in front of the positions and n after them, clip each to n, and pair neighbours.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

CSV Transpose & Unpivot Paste a small table and transpose it: rows become columns, as np.transpose does to the axes of a 2-D array.

Every idea of the lesson comes back in the self-check, starting from the one your answers so far have found hardest:

Practice that adapts to you

Practise this lesson

18 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 18 years = kwh.reshape(3, 12). Does changing years[0, 0] change kwh[0]?

    Choose one answer.

    Show the answer: Question 1 of 18

    Answer: Yes: the reshape is a view of the same 36 values

    years is kwh read as 3 rows of 12; a write through one is a write to the other.

  2. Question 2 of 18 Year 1 begins 310, 280, 260 kWh for January to March. What is its first quarterly total in kwh.reshape(3, 4, 3).sum(axis=2)?

    Type a number.

    Show the answer: Question 2 of 18

    Answer: 850 kWh

    The last axis of (3, 4, 3) holds the three months of each quarter, so summing it adds 310 + 280 + 260 = 850, the first value the run printed.

  3. Question 3 of 18 Why does kwh.reshape(5, -1) raise a ValueError?

    Choose one answer.

    Show the answer: Question 3 of 18

    Answer: 36 values cannot be split into 5 rows of equal length

    The new shape must hold exactly as many values as the old one: the product of its lengths is the size.

  4. Question 4 of 18 A file stores a 2 × 3 matrix column by column, as MATLAB writes it: 0, 1, 2, 3, 4, 5. Which line rebuilds the matrix whose first row is 0, 2, 4?

    Choose one answer.

    Show the answer: Question 4 of 18

    Answer: np.array(values).reshape(2, 3, order="F")

    reshape never knows how the numbers were written; the order argument is the only place to say it.

  5. Question 5 of 18 Which call is the same as np.moveaxis(img, -1, 0) for a 3-D image?

    Choose one answer.

    Show the answer: Question 5 of 18

    Answer: np.transpose(img, (2, 0, 1))

    transpose takes the full new order of axes; moveaxis names only the axis that moves.

  6. Question 6 of 18 red has shape (4, 6). What shape does np.expand_dims(red, -1) have?

    Choose one answer.

    Show the answer: Question 6 of 18

    Answer: (4, 6, 1)

    expand_dims(x, axis) is x with a length-1 axis inserted at axis, the same as indexing with np.newaxis there.

  7. Question 7 of 18 A batch normally has shape (n, 3, 4, 6). Why is np.squeeze(batch, axis=0) safer than np.squeeze(batch) for removing the batch axis?

    Choose one answer.

    Show the answer: Question 7 of 18

    Answer: With no axis, squeeze removes every length-1 axis, so a batch of one image with one channel would lose its channel axis too

    Shapes that change with the data, such as a batch of one, are where an unnamed squeeze removes too much.

  8. Question 8 of 18 What does np.column_stack([a, b]) give for a = [1, 2, 3] and b = [4, 5, 6]?

    Choose one answer.

    Show the answer: Question 8 of 18

    Answer: [[1, 4], [2, 5], [3, 6]]

    column_stack is stack with axis=1 for 1-D inputs; for 2-D inputs it joins as hstack does.

  9. Question 9 of 18 Ten arrays of shape (3, 4) are stacked with np.stack(arrays, axis=1). What shape results?

    Choose one answer.

    Show the answer: Question 9 of 18

    Answer: (3, 10, 4)

    NumPy's reference: axis is the index of the new axis in the dimensions of the result.

  10. Question 10 of 18 In the run, np.block joined a (2, 3) array of ones with a (2, 1) column of zeros on the first row of blocks, and a (1, 3) row of zeros with a (1, 1) block on the second. Why is the result (3, 4)?

    Choose one answer.

    Show the answer: Question 10 of 18

    Answer: Each inner list is joined along the last axis, (2, 4) and (1, 4), and then the rows of blocks along the first, 2 + 1 = 3 rows

    np.block reads like the matrix you would write on paper: rows of blocks, each row joined side by side.

  11. Question 11 of 18 What does np.split(np.arange(10), [3, 7]) give?

    Choose one answer.

    Show the answer: Question 11 of 18

    Answer: [0, 1, 2], [3, 4, 5, 6] and [7, 8, 9]

    An integer means that many equal parts; a list means the positions of the cuts.

  12. Question 12 of 18 What does np.split(np.arange(7), 3) do?

    Choose one answer.

    Show the answer: Question 12 of 18

    Answer: It raises ValueError: array split does not result in an equal division

    Use split when unequal parts would mean a bug in the data, and array_split when they are expected.

  13. Question 13 of 18 np.tile(t, (1000, 1)) for the three int64 values of t. How many bytes does the result own?

    Type a number.

    Show the answer: Question 13 of 18

    Answer: 24000 bytes

    tile writes every repeat: 1000 × 3 values × 8 bytes = 24,000 bytes, as the run printed. broadcast_to shows the same shape while reading t's 24 bytes.

  14. Question 14 of 18 What is np.repeat([1, 2, 3], [3, 0, 1])?

    Choose one answer.

    Show the answer: Question 14 of 18

    Answer: [1, 1, 1, 3]

    With a list of counts, repeat can expand rows of a table by a frequency column.

  15. Question 15 of 18 view = np.broadcast_to(t, (1000, 3)) reports nbytes 24000. How much memory does it hold?

    Choose one answer.

    Show the answer: Question 15 of 18

    Answer: None of its own: it reads the 24 bytes of t through strides (0, 8)

    nbytes is size times itemsize; for broadcast views and other views it says nothing about owned memory.

  16. Question 16 of 18 Old code calls np.row_stack([a, b]). What happens on NumPy 2.5.3?

    Choose one answer.

    Show the answer: Question 16 of 18

    Answer: AttributeError: the function was removed; use np.vstack([a, b])

    np.vstack is the same operation and works on every NumPy version.

  17. Question 17 of 18 What replaces the in-place z.resize((2, 3)), which the NumPy 2.5 release notes deprecate?

    Choose one answer.

    Show the answer: Question 17 of 18

    Answer: z = np.resize(z, (2, 3)), which returns a new array

    The run caught no warning for z.resize on 2.5.3, so do not wait for one: write np.resize, which repeats the values where the in-place method filled zeros, or np.reshape when the number of values stays the same.

  18. Question 18 of 18 Why does NumPy 2.5 deprecate assigning to arr.shape and arr.dtype?

    Choose one answer.

    Show the answer: Question 18 of 18

    Answer: Changing an array in place is unsafe when the array is shared, for example by several threads

    reshape and view make a new header and leave every other reference to the array as it was.

Interview questions

Warm-up (fresher): What is the difference between np.concatenate and np.stack?

np.concatenate joins arrays along an axis they already have, so the result has the same number of axes and that axis gets longer: two vectors of shape (3,) become one of shape (6,). np.stack adds a new axis and puts the arrays side by side along it, so the result has one more axis: the same two vectors become (2, 3), or (3, 2) with axis=1. stack needs inputs of exactly the same shape, while concatenate needs the same number of axes and matching lengths everywhere except the joined axis. Both copy the data into a new array. vstack, hstack and column_stack are shortcuts that turn 1-D inputs into rows or columns first.

Two interview questions take the lesson to real code, a batch of images reshaped instead of transposed and a table grown row by row; the module’s question page lists the first steps and follow-ups:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A model expects image batches as (batch, channels, height, width). A colleague converts a (32, 224, 224, 3) batch with batch.reshape(32, 3, 224, 224). The shapes match and training runs, but accuracy stays poor. What went wrong, how do you fix it, and what does the fix cost?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A script builds a table of 100,000 rows with result = np.vstack([result, row]) inside a loop and takes many seconds, although 10,000 rows took a fraction of a second. Explain why the time grows so fast, estimate the work, and rewrite it.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • reshape reads the values in C order (last index fastest) or F order (first index fastest) and lays them into the new shape in the same order; one length may be -1, and a reshape that strides can describe is a view.
  • moveaxis, transpose and swapaxes reorder axes by changing strides only, expand_dims adds an axis of length 1, and squeeze with a named axis removes exactly that one.
  • concatenate makes an existing axis longer and stack adds a new one; vstack, hstack, column_stack and np.block are shortcuts, and every join copies.
  • split cuts into equal parts or at given positions, array_split also into unequal ones, and every part is a view of the original.
  • tile and repeat write every repeat into new memory, while broadcast_to gives a read-only view with stride 0.
  • On NumPy 2.5, write np.reshape, arr.view(dtype), np.vstack and np.resize instead of assigning to shape or dtype, calling np.row_stack or resizing in place.

Terms in this lesson

deprecation
A notice that a feature will be removed in a later release. NumPy warns with a DeprecationWarning while the feature still works, then removes it some releases later, as it did with np.row_stack.
read-only view
A view that NumPy refuses to write through, such as the result of np.broadcast_to, where many positions read the same memory. Its flags.writeable is False, and a write raises ValueError.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.