v9.1.1
Menu

Chapter 2: NumPy & Data Wrangling

Master NumPy arrays for numerical computing, Pandas DataFrames for data manipulation, and essential data preprocessing techniques including cleaning, scaling, and encoding.

Topics
9
Videos
9
Min read
83

Chapter Overview

NumPy is the foundation of scientific computing in Python. Every image processing, machine learning, and data science library builds on NumPy arrays. Whether you're processing images pixel by pixel, training neural networks, or analyzing tabular datasets, NumPy arrays are at the core.

Why NumPy matters:

  1. Universal data structure: Images are 2D/3D arrays, feature matrices are 2D arrays, neural network weights are multi-dimensional arrays
  2. Vectorized operations: Process millions of elements without explicit loops -- 10-100x faster than pure Python
  3. Memory efficiency: Contiguous storage, typed arrays, views instead of copies
  4. Interoperability: OpenCV, PyTorch, TensorFlow, scikit-learn all use NumPy-compatible arrays

Pandas builds on NumPy to provide labeled, tabular data structures. A DataFrame is like a spreadsheet in Python: rows represent observations, columns represent features, and you can refer to data by name rather than position. Pandas excels at loading data from various sources, handling missing values, merging datasets, and computing aggregations.

Real-world data is messy. You'll encounter missing values, outliers, inconsistent formats, and features on vastly different scales. Data preprocessing often takes 80% of a data scientist's time, and this chapter teaches the essential techniques.

What you'll learn:

  1. Array Creation & Operations -- Building arrays, understanding shape/dtype, vectorized computation
  2. Indexing & Slicing -- Extracting regions, channels, and subsets efficiently
  3. Broadcasting -- Writing concise code for element-wise operations across different shapes
  4. Reshaping -- Converting between image formats (HWC, CHW, batched) and data layouts
  5. Statistics -- Computing histograms, means, distributions, and aggregations
  6. Linear Algebra -- Matrix operations for transforms, decompositions, and dimensionality reduction
  7. Pandas DataFrames -- Loading, exploring, filtering, and transforming tabular data
  8. Data Cleaning -- Handling missing values, outliers, and data quality issues
  9. Feature Preparation -- Scaling, encoding, and transforming features for ML algorithms

These skills directly apply to every CV and ML task: loading images, preprocessing data, applying filters, preparing features, and feeding data into models.

Chapter Roadmap

Click any topic to jump in

Explainer video
1
Array Creation

Creating N-dimensional arrays, dtypes, and vectorized operations — the foundation of numerical computing in Python.

Creating ArraysArray Shape and DimensionsData Types (dtype)Special Array ConstructorsVectorizationUniversal Functions (ufuncs)
Accessing and combining

Selection and shape alignment

2
Indexing & Slicing

Basic, advanced, and boolean indexing with views vs copies — efficiently selecting data from arrays.

Basic IndexingSlicing SyntaxAdvanced IndexingViews vs CopiesBoolean Indexing
3
Broadcasting

How NumPy aligns arrays of different shapes for element-wise operations — eliminating explicit loops.

Element-wise OperationsBroadcasting RulesCommon Broadcasting PatternsUniversal Functions (ufuncs)
Transforming structure and content

Geometry and summary statistics

4
Reshaping

Reshape, transpose, stack, and concatenate — manipulating array geometry without copying data.

ReshapeFlatten and RavelTranspose and AxesStacking and Concatenating
5
Statistics

Mean, std, percentiles along any axis — summarizing data in one vectorized call.

Basic StatisticsAxis ParameterPercentiles and HistogramsCumulative Operations
Core math operations
6
Linear Algebra

Matrix multiply, SVD, eigendecomposition — the core operations behind PCA, regression, and neural networks.

Matrix MultiplicationTranspose and InverseEigendecompositionSVD (Singular Value Decomposition)
Real-world data workflows

DataFrames, cleaning, and features

7
Pandas DataFrames

Labeled tabular data with method chaining — the standard for data wrangling in ML.

Series vs DataFrameMethod Chaining
8
Data Cleaning

Missing values, outlier detection, and imputation — preparing real-world data for models.

Missing Value StrategiesOutlier Detection
9
Feature Prep

Scaling, normalization, encoding — transforming raw features into model-ready inputs.

Feature ScalingOne-Hot Encoding

Topics

9 topics in this chapter

In the Python chapter you worked with ordinary lists and loops, but lists are a poor fit for numerical work: a list is a chain of pointers to separate objects, list + list concatenates instead of adding, and looping over a million numbers element by element is painfully slow. Yet every image, table, or signal in this plan is a grid of numbers you want to add, scale, and transform as one unit. That need is what NumPy arrays solve, and this topic is the foundation every later one -- indexing, broadcasting, reshaping, statistics -- builds on. You will first construct arrays from lists and from built-in constructors like zeros and arange, then learn to read an array's three defining properties: shape (the tuple of axis lengths), ndim (how many dimensions), and dtype (the type of each element). Finally you meet vectorization and universal functions, which apply arithmetic or math to every element in one operation instead of a Python loop, often 10-100x faster. Getting shape and dtype right up front prevents the most common bugs in image and ML code.

Definition

A NumPy array is a dense n-dimensional grid of values of a single element type, stored in one contiguous block of memory and addressed by an integer tuple called its shape. Its three defining properties are shape (the length of every axis), ndim (the number of axes, equal to len(shape)), and dtype (the element type). All operations are vectorized, each applying element-wise across the grid in one compiled pass.

In this topic

  1. 1Creating Arrays
  2. 2Array Shape and Dimensions
  3. 3Data Types (dtype)
  4. 4Special Array Constructors
  5. 5Vectorization
  6. 6Universal Functions (ufuncs)
1 of 6
Creating Arrays

np.array([data],dtype)\texttt{np.array}([\text{data}], \texttt{dtype})

Arrays are the core data structure of scientific Python: unlike a list of pointers, an ndarray stores elements of one type in one contiguous block of memory. np.array([data]) builds an array from a Python list, a nested list such as [[0,128],[255,64]] becoming a 2D array with one row per inner list. NumPy infers the dtype (element type) unless you pass dtype=..., e.g. dtype=np.float32. data may be any nested sequence, but it must be rectangular -- ragged rows raise an error. Constructors like zeros, ones, and randint build such arrays directly. This inherits Python's lists yet adds shape, dtype, and ndim, read in the next concept.

Mathematical Intuition

np.array allocates a single contiguous block of typed memory, unlike a list-of-lists, which stores pointers scattered across the heap; iterating the latter costs O(n) pointer-chases and cache misses. A contiguous float64 buffer lets the CPU prefetcher stream data at near-DRAM bandwidth, typically 10-50 GB/s. Construction from a list still copies every element, so np.array is O(n) in size and pays a conversion cost that constructors like zeros avoid by writing a known value directly.

Example:

Create a 640x480 grayscale image initialized to gray (128). Then create same-sized random noise image.

2 of 6
Array Shape and Dimensions

shape=(d0,d1,...,dn)\texttt{shape} = (d_0, d_1, ..., d_n)

shape is a tuple (d_0,...,d_n) listing each axis's length; for an RGB image (480, 640, 3) the axes are height, width, and channels. ndim counts the axes -- 3 here -- and equals len(shape). size is the total element count, the product 4806403 = 921,600. dtype is the element type, e.g. uint8 or float32. Convention matters: numpy's image shape is (height, width, channels), the opposite of some graphics APIs, and shape[0] is the row (y) count, not the width. These properties are read in nearly every image or ML computation. This reads off what the Creating Arrays constructors attached.

Mathematical Intuition

shape is stored alongside a strides tuple giving the byte offset between consecutive elements along each axis. Accessing (i,j,k) computes the byte offset is_0 + js_1 + k*s_2 in O(1) -- pure pointer arithmetic on the flat buffer, no search or hash. A contiguous array has a fixed stride per axis (row stride equals row length times dtype size), so the address is exact even though no per-element index list exists. This bounded layout is why random access is as cheap as a contiguous scan, and it foreshadows how the reshaping concept later reinterprets the same bytes.

Example:

cv2.imread() returns shape (720, 1280, 3). What are height, width, channels? How many total pixels?

3 of 6
Data Types (dtype)

uint8, float32, float64, ...\texttt{uint8, float32, float64, ...}

dtype names the element type an array stores: uint8 (unsigned 8-bit integer, values 0-255, one byte per element), float32 (4-byte single precision, typical for neural networks normalized to 0-1), and float64 (8-byte double, numpy's default for precise calculation). Choosing dtype controls both memory and precision: uint8 is standard for images, float32 for model tensors, float64 for careful math. astype converts, e.g. img.astype(np.float32)/255.0 maps an image into 0-1. The failure mode is overflow: arithmetic on uint8 wraps modulo 256, so 250+10 becomes 4 instead of raising an error, silently corrupting brightened images. dtype is fixed at creation but readable anytime, connecting back to the dtype the Creating Arrays concept set.

Mathematical Intuition

uint8 uses 1 byte per element, float32 4, float64 8. A 1080p RGB image is 108019203 about 6M elements -- 6 MB in uint8 versus 24 MB in float32, a 4x memory saving at the price of integer-only precision. Smaller dtypes also raise cache hit rates because more elements fit in the L1/L2 lines, so a float32 pipeline can be several times faster than float64 purely from memory traffic, not fewer flops. The tradeoff is range: uint8 is capped at 255, which is the overflow failure mode.

Example:

Brighten uint8 image by adding 50. Pixel at 220 becomes 270 -- but uint8 max is 255! What happens? How to fix?

4 of 6
Special Array Constructors

zeros, ones, arange, linspace, eye\texttt{zeros, ones, arange, linspace, eye}

zeros and ones build arrays filled with 0 or 1, e.g. np.ones((3,3))*5 gives a 3x3 of fives, while np.full((3,3),7) fills any constant directly. arange produces a range such as np.arange(0,10,2) = [0,2,4,6,8] -- start, stop-exclusive, step -- ideal for coordinate indices. linspace returns evenly spaced points, np.linspace(0,1,5) = [0,0.25,0.5,0.75,1], guaranteeing the count including both endpoints, which arange cannot: arange(0,1,0.25) stops before 1. eye gives an identity matrix np.eye(3) and diag places a vector on the diagonal. empty allocates uninitialized memory fastest when every element will be overwritten. These are the fast, typed relatives of the list-building you met in Creating Arrays.

Mathematical Intuition

np.zeros and np.ones perform one memset-style fill over a contiguous buffer, so they run in O(n) at near-peak memory bandwidth; their cost is mostly the bytes written. np.arange emits values by computing start + i*step in compiled code, never materializing a Python int per element, unlike a list comprehension that allocates one object per value. np.linspace precomputes the step as (stop-start)/(num-1) and hits the endpoints exactly. This is why these constructors outpace equivalent Python loops by orders of magnitude even though the logical work looks identical.

Example:

Create gradient image: 256x256, where pixel value = column index (0-255 left to right). Horizontal gradient.

5 of 6
Vectorization

c=a+b\mathbf{c} = \mathbf{a} + \mathbf{b} where ci=ai+bic_i = a_i + b_i

Vectorization means expressing an operation over an entire array at once rather than looping in Python. Here a and b are same-shaped operands, c the output, and the operation is element-wise: c_i = a_i + b_i for each index i. This is the payoff promised by Creating Arrays -- one dispatch over the whole buffer instead of a Python-level iteration per element, typically 10-100x faster. It fails when shapes do not align, or when you expect matrix multiplication, which is @, not this element-wise plus. Universal functions in the next concept generalize the very same idea to exp and log.

Mathematical Intuition

A Python for-loop pays roughly 100 ns of interpreter overhead per iteration. NumPy dispatches one C function over the whole buffer, processing several elements per SIMD instruction (AVX2 can handle 4-8 float64s at once). For n = 10^6 elements, loop overhead alone is about 100 ms versus roughly 1 ms for the vectorized call -- a factor of order O(n) in overhead that grows with array size, so vectorization matters more exactly when the data is large.

Example:

Compute squared L2 norm: sum(x_i^2) for x with 1 million elements. Compare: Python loop vs NumPy.

6 of 6
Universal Functions (ufuncs)

A ufunc (universal function) is an element-wise mathematical function numpy applies to every element of an array in one optimized C pass: np.exp, np.log, and np.sin are the common ones. np.exp(x) computes e^{x_i} for each element, the engine of softmax and logistic-sigmoid; np.log undoes exponentials; np.sin applies trigonometry. Unlike a Python loop that calls math.exp per value, a ufunc dispatches once over the whole buffer with a dtype-selected implementation, so it scales to millions of elements with no per-item overhead. They fail by overflow: np.exp(800.0) returns inf in float64. This generalizes the element-wise view from Vectorization to arbitrary functions.

Mathematical Intuition

Ufuncs are compiled C loops with a type-dispatch table that selects an implementation per dtype, so the same np.exp covers int, float32, and float64 without Python-level branching. np.exp uses a polynomial and rational minimax approximation from the Cephes library, accurate to about one ulp at float64. The practical boundary is exponential overflow: e^{710} is about 1.5e308, already near float64's maximum, and e^{800} returns inf. Softmax subtracts max(x) to keep every exponent at most 0, so the result stays finite and, being a scale-invariant shift, yields identical probabilities.

Example:

Compute softmax: exp(x_i) / sum(exp(x_j)) for array x = [1, 2, 3].

Code Examples

Building a gray image, a gradient, and reading shape

import numpy as np

gray = np.full((480, 640), 128, dtype=np.uint8)
grad = np.tile(np.arange(256), (256, 1)).astype(np.uint8)

print("shape", gray.shape, "| ndim", gray.ndim, "| size", gray.size, "| dtype", gray.dtype)
print("gray pixel", int(gray[0, 0]))
print("linspace", np.linspace(0, 1, 5))
print("grad corners", int(grad[0, 0]), int(grad[0, 255]), int(grad[255, 0]), int(grad[255, 255]))

Prints shape (480, 640), ndim 2, size 307200, dtype uint8; gray pixel 128; linspace [0. 0.25 0.5 0.75 1. ] and gradient corners 0 255 0 255, confirming that grad[r, c] equals the column index c and every row is the identical horizontal ramp.

Vectorized sum, uint8 overflow, and a stable softmax

import numpy as np

x = np.arange(1_000_000, dtype=np.float64)
print("sum of squares", f"{float(np.sum(x * x)):.5e}", "| dot", f"{float(x @ x):.5e}")

a = np.array([250], dtype=np.uint8)
print("uint8 250 + 10 wraps to", int((a + 10)[0]))

v = np.array([1.0, 2.0, 3.0])
e = np.exp(v)
print("exp", np.round(e, 2))
print("softmax", np.round(e / e.sum(), 2))

big = np.array([900.0, 905.0, 910.0])
z = big - big.max()
print("stable softmax", np.round(np.exp(z) / np.sum(np.exp(z)), 3))

The vectorized sum of squares and the dot product both equal 3.33333e+17, one compiled C pass over a million elements. uint8 250 + 10 wraps modulo 256 to 4 instead of 260. exp([1,2,3]) = [2.72 7.39 20.09] and the softmax = [0.09 0.24 0.67]. Finally, subtracting the max lets softmax of [900,905,910] evaluate stably to [0. 0.007 0.993] instead of overflowing to inf.

Theory Exercise

Problem:

You have an RGB image stored as a NumPy array. What are its shape, ndim, and total number of pixels if the image is 1920x1080?

Hints:
  • RGB images have 3 channels
  • Shape is (height, width, channels) in NumPy/OpenCV
  • ndim is the number of dimensions in the shape tuple

Coding Exercise

Problem:

Create two 2D arrays of shape (3, 3) and compute their matrix multiplication using np.dot().

Hints:
  • Use np.array() or np.random.randn()
  • Matrix multiplication: C = A @ B