numpy Essentials
The C-speed foundation under every dataframe
numpy provides the typed arrays under pandas and almost every data library. Knowing it explains why vectorization is fast and where memory actually goes.
What you will learn
- Build and manipulate typed numpy arrays
- Explain broadcasting and use it correctly
- Explain the memory cost of object dtypes and copies
What is this?
numpy is a C library exposed to Python that stores homogenous typed arrays and operates on them as whole vectors. pandas DataFrames are built on numpy buffers; every vectorized operation you love is numpy under the hood.
Why do you need it?
- Understanding numpy explains how pandas gets its speed and where it loses it.
- Broadcasting is the missing intuition that unlocks concise numeric transforms.
- Dtype choice controls memory: one 8-byte float vs one Python-object pointer with full object overhead.
Mental model
A conveyor belt of identical boxes
A numpy array
A homogeneous array lets a machine handle every box identically at once — but the boxes must be the same shape and type. Break that rule and you fall back to shuffling each box by hand.
How it works
- `np.array([...])` allocates one contiguous buffer with a fixed dtype and shape.
- Vectorized ops (`arr * 2`) loop in C over the buffer, not in Python.
- Broadcasting stretches smaller arrays across bigger ones when shapes are compatible.
- Views share a buffer (`arr[::2]`, `arr.reshape`) while copies allocate new memory.
- Mixed types force object dtype — Python objects, per-element overhead, 10-100x slower.
Why vectorization is fast: one C pass vs N Python calls.
Examples
Broadcasting a scalar across an array
import numpy as npa = np.array([10.0, 20.0, 30.0])b = np.array([[1, 2], [3, 4]])print(a * 0.08) # scalar broadcastprint(b.sum(axis=1)) # keepdims meaning: row sumsScalar multiplication and axis-sum are single C operations, not loops.
In production
- A performance-sampling layer computes percentiles with np.percentile over millions of floats in milliseconds.
- A masking step uses boolean arrays (`arr[mask]`) to select rows cheaply before a pandas transform.
- A time-bucket encoder converts epoch arrays to bucket indices with integer arithmetic — no Python loop.
Best practices
- Keep arrays homogenous — matching dtypes are the whole performance story.
- Know whether you are viewing or copying (`arr.view()`, `.reshape()` return views) to bound memory.
- Use `np.where`, `np.select` and boolean masks instead of Python conditionals.
- Freeze numpy in requirements; its C ABI changes can break prebuilt wheels.
Common mistakes
- Building an array with `np.array([...])` from Python objects in a loop, then wondering why it's slow.
- Copying huge arrays implicitly (e.g. `arr[:] = ...` vs `arr = arr + 1` allocating new memory).
- Forgetting that mixed types become object arrays — silently 50x slower.
- Using `np` indexing in a Python loop instead of vectorized selection.
Common issues
MemoryError with a modest-sized arrayObject dtype (Python floats) or accidental copies using 100 bytes/element
Coerce to np.float32/float64 explicitly and audit in-place ops.
Broadcasting error: operands could not be broadcast togetherArray shapes incompatible for an operation
Check .shape; align with .reshape/.expand_dims so trailing dimensions match.
Debugging tips
- Check `arr.dtype` and `arr.itemsize` when something is mysteriously slow or large.
- If an op degenerates to Python objects, inspect the dtype of the input arrays first.
- Use `np.broadcast_to` errors to understand shape mismatches — they name the misaligned axis.
Interview checkpoint
Quick quiz
What determines the memory footprint of a numpy array?
A slice like arr[::2] always copies the underlying data into new memory.