A 3D array in Python is a NumPy array with three dimensions, created most easily by reshaping a flat one:
import numpy as np
a = np.arange(24).reshape(2, 3, 4) # 2 blocks, 3 rows, 4 columns
a.shape # (2, 3, 4)
Read the shape from the outside in: 2 blocks, each holding 3 rows, each holding 4 columns. That ordering is what makes indexing and axis= make sense.
All output here comes from real runs on Python 3.12.5, NumPy 2.5.3.
Creating a 3D array in NumPy
Writing one out by hand shows the structure clearly, even if it’s not how you’d do it in practice:
import numpy as np
# written out by hand: a list of 2D blocks
a = np.array([[[1, 2, 3],
[4, 5, 6]],
[[7, 8, 9],
[10, 11, 12]]])
print(a)
print("\nshape:", a.shape, " ndim:", a.ndim, " size:", a.size)
print("read the shape as (blocks, rows, columns)")
Output:
[[[ 1 2 3]
[ 4 5 6]]
[[ 7 8 9]
[10 11 12]]]
shape: (2, 2, 3) ndim: 3 size: 12
read the shape as (blocks, rows, columns)
Notice how the printout groups things. NumPy prints a blank line between blocks, so you can count the outermost dimension by counting the groups.
Python lists cannot do this on their own. A list of lists of lists works, but it has no shape, no axis and none of the arithmetic.
Ways to build a 3D array
In practice you rarely type the values. These five cover nearly everything:
import numpy as np
print("zeros:", np.zeros((2, 3, 4)).shape)
print("ones :", np.ones((2, 3, 4)).shape)
print("full :", np.full((2, 2, 2), 7).shape)
print("random:", np.random.rand(2, 3, 4).shape)
print()
# the usual way: count up, then fold into shape
a = np.arange(24).reshape(2, 3, 4)
print(a)
print("\n2 x 3 x 4 = 24, which is why arange(24) fits exactly")
Output:
zeros: (2, 3, 4)
ones : (2, 3, 4)
full : (2, 2, 2)
random: (2, 3, 4)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
2 x 3 x 4 = 24, which is why arange(24) fits exactly
| Call | Gives you |
|---|---|
np.zeros((2, 3, 4)) | All zeros |
np.ones((2, 3, 4)) | All ones |
np.full((2, 2, 2), 7) | Every element set to 7 |
np.random.rand(2, 3, 4) | Random floats from 0 to 1 |
np.arange(24).reshape(2, 3, 4) | 0 to 23, folded into shape |
The dimensions must multiply to the number of elements you have. 2 × 3 × 4 is 24, which is why arange(24) fits. There is more on sizing in NumPy empty arrays.
Indexing and slicing a 3D array
Each index you supply removes one dimension from the result:
import numpy as np
a = np.arange(24).reshape(2, 3, 4)
print("a[0] ->", a[0].shape, " one whole block")
print("a[0, 1] ->", a[0, 1].shape, " one row of that block")
print("a[0, 1, 2] ->", a[0, 1, 2], " a single number")
print()
print("slices keep the dimensions you leave in:")
print("a[:, 0, :] ->", a[:, 0, :].shape, " row 0 from every block")
print("a[..., 0] ->", a[..., 0].shape, " column 0 from every row")
print()
print("a[..., 0] means 'all earlier axes, then index 0 on the last one':")
print(a[..., 0])
Output:
a[0] -> (3, 4) one whole block
a[0, 1] -> (4,) one row of that block
a[0, 1, 2] -> 6 a single number
slices keep the dimensions you leave in:
a[:, 0, :] -> (2, 4) row 0 from every block
a[..., 0] -> (2, 3) column 0 from every row
a[..., 0] means 'all earlier axes, then index 0 on the last one':
[[ 0 4 8]
[12 16 20]]
a[0]— the first block, shape(3, 4).a[0, 1]— one row of that block, shape(4,).a[0, 1, 2]— a single value.a[:, 0, :]— row 0 from every block, shape(2, 4).a[..., 0]— the ellipsis fills in whatever axes you did not mention.
... is worth learning. a[..., 0] means “the first item along the last axis”, and it keeps working if the array gains dimensions later.
What does axis mean in a NumPy 3D array?
This is the part that actually trips people up, and the rule is simpler than it looks: the axis you name is the one that disappears.
import numpy as np
a = np.arange(24).reshape(2, 3, 4)
# summing along an axis REMOVES that axis
for axis in (0, 1, 2):
result = a.sum(axis=axis)
print(f"sum(axis={axis}) -> shape {result.shape}")
print()
print("axis=0 collapses the 2 blocks, leaving 3x4:")
print(a.sum(axis=0))
print()
print("axis=2 collapses the 4 columns, leaving 2x3:")
print(a.sum(axis=2))
Output:
sum(axis=0) -> shape (3, 4)
sum(axis=1) -> shape (2, 4)
sum(axis=2) -> shape (2, 3)
axis=0 collapses the 2 blocks, leaving 3x4:
[[12 14 16 18]
[20 22 24 26]
[28 30 32 34]]
axis=2 collapses the 4 columns, leaving 2x3:
[[ 6 22 38]
[54 70 86]]
(2, 3, 4) loses whichever axis you sum along.| Operation | Result shape | Meaning |
|---|---|---|
a.sum(axis=0) | (3, 4) | Add the blocks together |
a.sum(axis=1) | (2, 4) | Add the rows within each block |
a.sum(axis=2) | (2, 3) | Add the columns within each row |
Check it against the numbers. a.sum(axis=0) starts with 12, which is 0 + 12: the first element of block one plus the first of block two.
The same rule governs mean, max, min and the rest. Name the axis you want gone.
An RGB image is a 3D array
If the block-row-column model still feels abstract, here is a 3D array you already understand:
import numpy as np
import matplotlib.pyplot as plt
# a colour image IS a 3D array: (height, width, 3)
height, width = 120, 200
picture = np.zeros((height, width, 3), dtype=np.uint8)
picture[:, :, 0] = np.linspace(0, 255, width) # red rises left to right
picture[:, :, 1] = np.linspace(0, 255, height)[:, None] # green rises top to bottom
picture[:, :, 2] = 120 # blue is constant
print("shape:", picture.shape, "-> (height, width, colour channels)")
print("one pixel:", picture[60, 100], "= [R G B]")
fig, axes = plt.subplots(1, 4, figsize=(12, 3))
axes[0].imshow(picture); axes[0].set_title("all 3 channels")
for i, name in enumerate(("Red", "Green", "Blue")):
axes[i + 1].imshow(picture[:, :, i], cmap="gray", vmin=0, vmax=255)
axes[i + 1].set_title(f"{name}: picture[:, :, {i}]")
for ax in axes: ax.axis("off")
plt.tight_layout(); plt.show()
Output:
shape: (120, 200, 3) -> (height, width, colour channels)
one pixel: [128 128 120] = [R G B]
(120, 200, 3): height, width, and one layer per colour. Blue is flat grey because it is constant.Every photo you have ever opened is this shape. picture[:, :, 0] is the red layer, and the three greyscale panels are exactly that slice.
A single pixel is picture[y, x], which returns three numbers rather than one. That is the third dimension in the most literal form there is.
It also explains a common Matplotlib error: imshow accepts (h, w) or (h, w, 3), and complains about anything else. See plotting NumPy arrays for the 2D case.
Looping over a 3D array
Iterating gives you blocks, not numbers, which isn’t what most people expect the first time:
import numpy as np
a = np.arange(24).reshape(2, 3, 4)
# iterating gives you 2D blocks, not numbers
for i, block in enumerate(a):
print(f"block {i} has shape {block.shape} and sums to {block.sum()}")
print()
# to reach every individual value
print("flat values:", list(a.ravel())[:8], "...")
print()
# and with their coordinates
for index, value in list(np.ndenumerate(a))[:5]:
print(f" {index} -> {value}")
Output:
block 0 has shape (3, 4) and sums to 66
block 1 has shape (3, 4) and sums to 210
flat values: [np.int64(0), np.int64(1), np.int64(2), np.int64(3), np.int64(4), np.int64(5), np.int64(6), np.int64(7)] ...
(0, 0, 0) -> 0
(0, 0, 1) -> 1
(0, 0, 2) -> 2
(0, 0, 3) -> 3
(0, 1, 0) -> 4
ravel() flattens to one dimension when you genuinely want every value in order.
np.ndenumerate gives the coordinates alongside each value, which is the equivalent of enumerate for arrays of any shape.
That said, an explicit loop over a NumPy array is usually a sign you want a vectorised operation instead. Loops are slower by a wide margin.
Reshaping and transposing a 3D array
reshape changes the shape and keeps the order. transpose reorders the axes themselves:
import numpy as np
flat = np.arange(24)
print("flat :", flat.shape)
cube = flat.reshape(2, 3, 4)
print("reshaped :", cube.shape)
print("back flat :", cube.reshape(-1).shape, " -1 means 'work it out'")
print("to 2D :", cube.reshape(6, 4).shape)
print()
# transpose reorders the axes rather than the data
print("transpose(2, 0, 1):", cube.transpose(2, 0, 1).shape)
print()
try:
flat.reshape(2, 3, 5)
except ValueError as err:
print("reshape(2, 3, 5) ->", err)
Output:
flat : (24,)
reshaped : (2, 3, 4)
back flat : (24,) -1 means 'work it out'
to 2D : (6, 4)
transpose(2, 0, 1): (4, 2, 3)
reshape(2, 3, 5) -> cannot reshape array of size 24 into shape (2,3,5)
-1 lets NumPy work out the missing dimension.reshape(-1) is the idiomatic flatten. You can use -1 for exactly one dimension and NumPy computes it.
The error message from an impossible reshape is blunt but useful: it tells you the size and the shape you asked for, so the mismatch is easy to spot.
transpose(2, 0, 1) moves the last axis to the front, which is how you convert between image formats that put channels first or last. For the 2D version see transposing an array.
Related NumPy array guides:
- NumPy empty arrays
- Initialize a 2D array
- Transpose an array in Python
- NumPy linspace
- NumPy data types
- Plot a NumPy array with Matplotlib
Frequently asked questions
How do I create a 3D array in Python?
Use NumPy: np.arange(24).reshape(2, 3, 4), or np.zeros((2, 3, 4)) for an empty one. The constructors are listed in the NumPy array creation reference.
How do I read a 3D array shape?
From the outside in. (2, 3, 4) means 2 blocks, each with 3 rows, each with 4 columns.
What does axis mean in a 3D array?
The axis you name is the one that gets collapsed. sum(axis=0) on a (2, 3, 4) array returns a (3, 4) result.
How do I access a single element in a 3D array?
Give all three indices: a[0, 1, 2]. Fewer indices return a slice rather than a number.
What does a[…, 0] mean?
The ellipsis stands for every axis you did not mention, so it takes index 0 along the last axis only.
Is an image a 3D array?
Yes. A colour image has shape (height, width, 3), with one layer each for red, green and blue.
Why does reshape raise a ValueError?
The new dimensions must multiply to the existing number of elements. 24 values cannot become a (2, 3, 5) array, which needs 30.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn