Joining and splitting arrays
concatenate extends an axis; stack creates one. Everything else is shorthand.
np.concatenatenp.stacknp.vstacknp.hstacknp.splitnp.tilenp.repeatWatch it happen
Play it through, or step back and forth yourself.
pqTwo (2, 2) arrays. There are two genuinely different things you might mean by "put them together", and NumPy gives you a separate function for each.
The idea
There are two genuinely different things "put these arrays together" can mean, and getting them straight saves a lot of confusion.
concatenate extends an existing axis
np.concatenate([p, q], axis=0) # (2,2) + (2,2) -> (4,2)
np.concatenate([p, q], axis=1) # (2,2) + (2,2) -> (2,4)The rank stays the same — one axis just gets longer. Every other axis has to match exactly, which is what the error message means when it complains that "all the input array dimensions except for the concatenation axis must match".
stack creates a new axis
np.stack([p, q]) # (2,2) + (2,2) -> (2, 2, 2)
np.stack([p, q], axis=-1) # -> (2, 2, 2), but interleaved differentlyHere the rank goes up. All the inputs must have the same shape, since they're being laid side by side along a brand-new axis. This is how a list of images becomes a batch, and how a list of columns becomes a table.
That's the whole distinction: concatenate makes an axis longer, stack adds one. If you're ever unsure which you want, ask whether the answer should have more dimensions than the inputs.

The shorthands
np.vstack([p, q]) # like concatenate(axis=0)
np.hstack([p, q]) # like concatenate(axis=1) — but axis=0 for 1-D input
np.column_stack([x, y]) # two 1-D arrays become two columnsThese read nicely and occasionally surprise you: hstack on 1-D arrays joins end-to-end rather than making columns, because 1-D arrays only have one axis to join along. When the input rank might vary, prefer concatenate or stack with an explicit axis=. It's longer and it's never ambiguous.
Splitting
np.split(arr, 3) # into 3 equal parts — raises if it doesn't divide
np.split(arr, [2, 5]) # split before index 2 and before index 5
np.array_split(arr, 3) # allows uneven parts
np.vsplit / np.hsplit # the axis-specific shorthandsAll of these return a list of arrays, not an array — a common surprise when you try to do arithmetic on the result.
tile versus repeat
The classic mix-up, and worth ten seconds to get right:
x = np.array([1, 2, 3])
np.tile(x, 2) # [1 2 3 1 2 3] — the whole array, again
np.repeat(x, 2) # [1 1 2 2 3 3] — each element, againtile lays the pattern down repeatedly, like floor tiles. repeat duplicates each element where it stands. Both take an axis on higher-dimensional input.
One last note: joining always copies. There's no way to make two separately allocated arrays contiguous without moving bytes. So building an array by concatenating in a loop is quadratic and slow — collect into a Python list and concatenate once at the end, or pre-allocate with np.empty and fill it.

Practice
Write it yourself. The answer is there when you want it.
Putting the kettle on…
Starting up…
Write it yourself
not gradedConcatenate p and q along axis=0 and axis=1, printing each. Then print the shape np.stack gives instead — a new axis, not a longer one. Compare np.tile with np.repeat on [1, 2, 3]. Finish by splitting np.arange(6) into three.
Your turn
4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Join p and q so the result is (4, 2) — one on top of the other.
Now join them side by side, so the result is (2, 4).
Stack p and q along a new axis so the result is (2, 2, 2).
From np.array([1, 2, 3]), produce [1, 1, 2, 2, 3, 3] — each element doubled in place.
