NumPy · Module 1 of 13 · 4 concepts · about 5 min

NumPy Fundamentals

Constructing arrays with the shape and dtype you meant, rather than the ones inference gave you.

About NumPy: NumPy lets you work with a whole list of numbers at once. Instead of writing a loop to add or compare numbers one by one, you put them in an array and do it in a single line, and it runs much faster. Pandas, scikit-learn and PyTorch are all built on NumPy arrays, so this is the best place to start. You will begin by making arrays and picking out values, then learn to do maths on whole arrays, and finish with the common mistakes that make results wrong or code slow.

At work: Setting up the arrays every numeric job starts from: model inputs, price grids, sensor readings, image pixels.

np.array and np.asarray

What it is

np.array(obj) builds a new array from a nested sequence and copies by default. np.asarray(obj) returns the input unchanged if it is already a compatible array, and only copies otherwise.

Why it matters

asarray is the right first line of a function that accepts 'array-like' input: it normalises lists into arrays without paying for a copy when the caller already handed you an array.

In the real world: Turning a list of today's product prices from an API into an array so discounts and taxes can be applied to all of them at once.

def standardize(x):
    x = np.asarray(x, dtype=float)   # no copy if already a float array
    return (x - x.mean(axis=0)) / x.std(axis=0)

np.array([[1, 2], [3, 4]], dtype='float32')

Using asarray and then writing into the result. If the caller passed an array, you have just mutated theirs. Use np.array(x, dtype=float) (or np.asarray(x).copy()) when you intend to write.

Practise it: Pad Sequences to a Fixed Length

zeros, ones, full and empty

What it is

Shape-first constructors: np.zeros(shape), np.ones(shape), np.full(shape, value), and np.empty(shape), which allocates without initialising. The _like variants copy shape and dtype from an existing array.

Why it matters

Preallocating and filling by index is the correct replacement for building an array by appending, and zeros_like/empty_like keep the dtype so the fill does not silently upcast.

In the real world: Starting a sales-per-store-per-day grid at zero, then filling it in as orders are counted.

out = np.zeros((3, 4), dtype=np.float32)
mask = np.ones(10, dtype=bool)
np.full((2, 2), np.nan)             # float, because nan is a float
buf = np.empty_like(out)            # same shape and dtype, uninitialised

np.empty contains whatever was in that memory. Reading it before writing every position gives plausible-looking garbage that is reproducible often enough to be mistaken for a real result.

Practise it: Pad Sequences to a Fixed Length

arange versus linspace

What it is

np.arange(start, stop, step) counts by a step and excludes the stop. np.linspace(start, stop, num) divides an interval into a fixed number of points and includes the stop by default.

Why it matters

arange is for indices, linspace is for continuous ranges. With a float step, arange accumulates rounding error and the element count becomes unpredictable, which linspace does not.

In the real world: Making 24 hourly time slots for a delivery-demand forecast, or 50 evenly spaced prices to test how demand changes with price.

np.arange(5)                  # array([0, 1, 2, 3, 4])
np.arange(0, 1, 0.1).size     # 10 — but the last value is 0.9000000000000001
np.linspace(0, 1, 11)         # 11 points, exactly 0.0 .. 1.0
np.linspace(0, 1, 10, endpoint=False)

np.arange(0, 0.3, 0.1) can give three or four elements depending on the rounding of the accumulated step. Never use a float step when the element count matters.

astype, and when a copy happens

What it is

astype(dtype) returns a new array converted to that dtype, always a copy unless copy=False and the dtype already matches. Float-to-int truncates toward zero rather than rounding.

Why it matters

It is the one place a whole array's precision and memory changes, so it is worth being explicit: casting a float64 feature matrix to float32 halves the memory of everything downstream.

In the real world: Converting image pixels from whole numbers to decimals between 0 and 1 before feeding them to a product-photo classifier.

np.array([1.7, -1.7]).astype(int)      # array([ 1, -1]) — truncation, not rounding
np.rint([1.7, -1.7]).astype(int)       # array([ 2, -2]) — round first, then cast
X32 = X.astype(np.float32, copy=False)
np.array([1.0, np.nan]).astype(int)    # garbage: nan has no integer value

Casting an array containing nan or inf to an integer dtype. There is no integer nan, so you get an arbitrary sentinel (often the most negative integer) and no error.