Skip to main content
NumPy · lesson 2 of 13
NumPy beginner Lesson 2 of 13

Introduction to NumPy

Why NumPy exists: measure a Python list against an ndarray for memory and speed, install NumPy, and inspect your first array's dtype and shape.

By SkillByExample · Updated

What Is NumPy?

NumPy is the array library the rest of scientific Python is built on. Its ndarray holds many values of one type in a single block of memory, and its functions operate on the whole block at once — no Python loop required.

Memory: A List vs an Array

The same million integers, stored both ways:

import sys
import numpy as np

n = 1_000_000
py_list = list(range(n))
arr = np.arange(n)

list_bytes = sys.getsizeof(py_list) + sum(sys.getsizeof(x) for x in py_list)
print(f"list : {list_bytes / 1e6:.1f} MB")
print(f"array: {arr.nbytes / 1e6:.1f} MB")
print(arr.dtype, arr.itemsize, "bytes per element")
$ python memory.py
list : 36.0 MB
array: 8.0 MB
int64 8 bytes per element

The list pays for a pointer per slot plus a full Python int object per value (28 bytes each), so it lands at several times the array’s size. The array stores each value as a raw 8-byte int64 and nothing else — nbytes is exactly n * itemsize.

Speed: A Loop vs a Vectorized Operation

Doubling every element, once with a list comprehension and once with NumPy:

import timeit
import numpy as np

n = 10_000_000
py_list = list(range(n))
arr = np.arange(n)

t_list = min(timeit.repeat(lambda: [x * 2 for x in py_list], number=1, repeat=5))
t_np = min(timeit.repeat(lambda: arr * 2, number=1, repeat=5))

print(f"list comprehension: {t_list:.3f} s")
print(f"numpy arr * 2     : {t_np:.3f} s")
print(f"speedup           : {t_list / t_np:.0f}x")
$ python speed.py
list comprehension: 0.458 s
numpy arr * 2     : 0.055 s
speedup           : 8x

Each time is the best of five runs (timeit.repeat + min), which filters out background noise. Even so, the ratio is not a fixed number: rerunning this script on the same machine gave anywhere from about 8x to 26x, mostly because the NumPy side is so short that allocating the 80 MB result dominates it. The list side never came close. The comprehension executes Python bytecode ten million times; arr * 2 is a single call into a compiled loop that walks the array’s memory directly.

Installation

Install with pip install numpy (or conda install numpy), then confirm which version you have:

import numpy as np

print(np.__version__)
$ python version.py
2.5.3

Every example in this track was run against the version shown above. NumPy 2.x changed a few printed representations (scalars show their type), so if you are on 1.x some output will look slightly different.

Your First Array

import numpy as np

temps_c = np.array([22.1, 23.5, 19.8, 25.0, 21.3])

print(temps_c)
print(temps_c.dtype, temps_c.shape, temps_c.ndim, temps_c.size)

temps_f = temps_c * 9 / 5 + 32
print(temps_f)
print("mean:", temps_c.mean())
print("repr:", repr(temps_c.mean()))
$ python first_array.py
[22.1 23.5 19.8 25.  21.3]
float64 (5,) 1 5
[71.78 74.3  67.64 77.   70.34]
mean: 22.34
repr: np.float64(22.34)

NumPy inferred float64 from the decimal literals, and shape (5,) is a one-element tuple because the array has one axis. temps_c * 9 / 5 + 32 converted all five readings with no loop. The last two lines show NumPy 2’s scalar printing: print gives the bare number, while repr (what you see inside a list or at the REPL) names the type as np.float64(...).

A 2-D Array

import numpy as np

matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

print(matrix.shape)
print(matrix.ndim)
print(matrix.dtype)
print(matrix.nbytes)
$ python matrix_props.py
(3, 3)
2
int64
72

A nested list of equal-length rows becomes a 2-D array with shape (rows, columns). Integer literals default to int64 on Linux and macOS (and on Windows since NumPy 2.0), so nine elements take 72 bytes.

Practice

  1. Change the memory example to use np.arange(n, dtype=np.int32). Predict the new array size before running it.
  2. In the speed example, replace x * 2 with x ** 2 in both versions. Does the speedup grow or shrink?
  3. Build a 2 × 4 array of floats and print its shape, size, and nbytes.
Solution 1
import numpy as np

n = 1_000_000
arr = np.arange(n, dtype=np.int32)
print(f"array: {arr.nbytes / 1e6:.1f} MB")
print(arr.dtype, arr.itemsize, "bytes per element")
$ python practice_int32.py
array: 4.0 MB
int32 4 bytes per element

Halving the element width halves the array: int32 uses 4 bytes per value instead of 8.

Solution 2
import timeit
import numpy as np

n = 10_000_000
py_list = list(range(n))
arr = np.arange(n)

t_list = min(timeit.repeat(lambda: [x ** 2 for x in py_list], number=1, repeat=5))
t_np = min(timeit.repeat(lambda: arr ** 2, number=1, repeat=5))
print(f"list: {t_list:.3f} s  numpy: {t_np:.3f} s  speedup: {t_list / t_np:.0f}x")
$ python practice_square.py
list: 0.704 s  numpy: 0.035 s  speedup: 20x

The list side got slower (Python’s ** costs more than *), while NumPy’s time barely moved. Reruns on the same machine gave 15x to 20x, inside the range the doubling example showed, so don’t read much into any single ratio. What doesn’t change is that the list side does one interpreted step per element and NumPy does none.

Solution 3
import numpy as np

a = np.array([[1.0, 2.0, 3.0, 4.0],
              [5.0, 6.0, 7.0, 8.0]])
print(a.shape, a.size, a.nbytes)
$ python practice_shape.py
(2, 4) 8 64

Eight float64 values at 8 bytes each is 64 bytes.

Frequently Asked Questions

Why is NumPy faster than Python lists?
A NumPy array stores its values in one contiguous block of typed memory, like a C array, while a Python list stores pointers to separate Python objects. NumPy operations run as compiled loops over that raw memory instead of interpreting bytecode per element, so elementwise arithmetic is typically tens of times faster; measure it on your own machine with time.perf_counter.
When should I use NumPy instead of pandas?
Use NumPy for homogeneous numerical arrays: linear algebra, signal processing, image data, and custom machine-learning math. Use pandas when data has named columns, mixed types, or needs group-by, merge, or time-series resampling. pandas DataFrames are built on NumPy arrays, so the two work together.
Does NumPy use multiple CPU cores?
Ordinary elementwise NumPy operations run on a single thread. Matrix routines such as np.dot, the @ operator, and most of np.linalg call an optimized BLAS/LAPACK library (OpenBLAS or MKL), which can use several threads automatically for large inputs.
Why does NumPy print np.float64(22.34) instead of 22.34?
Since NumPy 2.0, the repr of a NumPy scalar includes its type, so a value inside a list or printed with repr shows as np.float64(22.34). Calling print() on the scalar directly, or formatting it with an f-string, still shows the plain number. Convert with float(x) or x.item() when you need a native Python value.