L
LLLOS.ai
Learn
L

Chapter 5 — Introduction To Numpy

Class 11 · Informatics Practices

Overview

Chapter 5 — Introduction To Numpy Master Diagram

This chapter introduces NumPy, the fundamental Python library for numerical computing used widely in data analysis, scientific computing and machine learning. Students learn why NumPy arrays (ndarray) are preferred over Python lists for numerical tasks — mainly for speed, memory efficiency and convenient vectorized operations. The chapter covers creating arrays, inspecting array attributes (shape, dtype, ndim, size), accessing and slicing elements, reshaping and transposing, common array-generation routines (arange, zeros, ones, linspace, eye, random), elementwise arithmetic and universal functions (ufuncs), aggregation functions (sum, mean, min, max, std), boolean indexing and simple broadcasting rules. Emphasis is on writing concise, efficient code for numerical problems and building a foundation for later topics in data handling and machine learning.

Learning Objectives

  • Define NumPy and ndarray and state their advantages over Python lists in terms of performance and functionality
  • Explain the meaning of dtype and shape attributes of an ndarray
  • Demonstrate creation of NumPy arrays from lists and tuples using np.array and other constructors (np.arange, np.zeros, np.ones, np.linspace, np.eye)
  • Apply indexing and slicing on one-dimensional and two-dimensional arrays to access, modify and assign values
  • Use array reshaping methods (reshape, ravel, flatten) and explain the difference between view and copy
  • Perform basic arithmetic, element-wise operations and universal functions (ufuncs) on arrays
  • Explain broadcasting rules and apply broadcasting to perform operations on arrays of different shapes
  • Compute aggregate statistical functions (sum, mean, median, min, max, std) on arrays and along specified axes

Topics in this chapter

20 topics · tap a topic title to jump straight to it.

💻1

Introduction to NumPy

💻 COMPUTER SCIENCE / IT

Introduction to NumPy

Key Point: Dot product (vectors a and b of length n): a · b = Σ_{i=1..n} a_i * b_i

NumPy (Numerical Python) is a fundamental Python library used for numerical computing. It provides the ndarray — a fast, N-dimensional array object — and many functions to perform mathematical, logical and statistical operations on arrays efficiently.

Why use NumPy?

  • Speed: NumPy arrays are implemented in C and optimized for performance, so operations on arrays are much faster than equivalent Python list operations.
  • Memory efficiency: Arrays store elements in contiguous memory, using less space than Python lists.
  • Vectorized operations: Element-wise arithmetic, reductions and many mathematical routines work without explicit Python loops.
  • Interoperability: Widely used in data science, machine learning, image processing and scientific computing.

Core concept — ndarray

An ndarray is a grid of elements of the same type, indexed by a tuple of nonnegative integers. Key attributes:

  • ndarray.ndim — number of dimensions (rank).
  • ndarray.shape — tuple giving the size in each dimension (rows, columns, ...).
  • ndarray.size — total number of elements.
  • ndarray.dtype — data type of the elements (int, float, bool, etc.).

Creating arrays

import numpy as np
a = np.array([1, 2, 3])            # 1D array
b = np.array([[1, 2], [3, 4]])     # 2D array (matrix)
c = np.zeros((2,3))                # zeros
d = np.ones(4)                     # ones
e = np.arange(0, 10, 2)            # 0,2,4,6,8
f = np.linspace(0, 1, 5)           # 5 values evenly spaced
g = np.random.rand(3,3)            # random numbers in [0,1)

Indexing and slicing

Similar to lists but supports multi-dimensional indexing. Examples:

element = b[0,1]        # first row, second column
row = b[1, :]            # second row
col = b[:, 0]            # first column
sub = b[0:2, 0:1]        # slice (rows 0..1, cols 0..0)

Vectorized operations & broadcasting

Arithmetic on arrays is element-wise:

x = np.array([1,2,3])
y = np.array([4,5,6])
print(x + y)    # [5,7,9]
print(x * 2)    # [2,4,6]
print(x * y)    # [4,10,18]

Broadcasting allows operations between arrays of different shapes when they are compatible: NumPy virtually expands the smaller array to match the shape of the larger one rather than copying data.

Common array operations

  • Reshape: arr.reshape(new_shape)
  • Transpose: arr.T
  • Aggregation: np.sum, np.mean, np.std, np.min, np.max
  • Linear algebra: np.dot (dot product / matrix multiply), np.linalg.inv, np.linalg.eig

Typical workflow

  1. Create or load data into ndarrays (from lists, files or libraries like pandas).
  2. Use vectorized operations and NumPy functions to compute results.
  3. Use Matplotlib (or other libraries) for visualization.

Simple example: mean and standard deviation of marks

marks = np.array([78, 85, 62, 90, 74])
mean = np.mean(marks)
std = np.std(marks)
print(mean, std)

Installation: pip install numpy (or use Anaconda which includes NumPy).

NumPy is a building block for data science and scientific computing — learning it makes tasks like matrix calculations, signal processing and image handling much easier and faster than plain Python lists.

📌 Examples
  • Image processing: A color image is a 3D NumPy array of shape (height, width, 3). You can crop, adjust brightness (multiply array by scalar), or convert to grayscale (weighted sum across the color channel).
  • Time series / sensors: Temperature readings over time are stored as 1D arrays. Use np.mean, np.std, and np.convolve for smoothing.
  • Finance: Closing stock prices as arrays — compute returns with vectorized differences and moving averages with convolution.
  • Student marks table: A 2D array where rows are students and columns are subjects. Use axis operations to get per-student totals or per-subject averages.
  • Scientific simulation: Positions, velocities of many particles stored as arrays — vectorized updates replace slow loops.
🧮 Formulas
  1. \[Dot product (vectors a and b of length n): a · b = Σ_{i=1..n} a_i * b_i\]
  2. \[Matrix multiplication (C = A × B): C_{ij} = Σ_{k} A_{ik} * B_{kj}\]
  3. \[Mean (average) of n values x_i: μ = (1/n) * Σ_{i=1..n} x_i\]
  4. \[Variance (population): σ^2 = (1/n) * Σ_{i=1..n} (x_i - μ)^2\]
  5. \[Broadcasting rule (summary): Two dimensions are compatible when they are equal or one of them is 1\]
    \[alignment occurs from trailing dimensions.\]
💻2

ndarray (N-dimensional array)

💻 COMPUTER SCIENCE / IT

ndarray (N-dimensional array)

Key Point: size = product(shape) # total elements, e.g., size = m * n for shape (m, n)

What is an ndarray?
An ndarray is NumPy's primary data structure: a homogeneous, fixed-size, N-dimensional array of items of the same data type. It stores data in contiguous memory and provides fast vectorized operations and convenient attributes to work with numeric data efficiently.

Key characteristics

  • Homogeneous: every element has the same dtype (e.g., int, float).
  • N-dimensional: can represent 1D (vectors), 2D (matrices/tables), 3D (images with color channels), etc.
  • Attributes: shape (dimensions), ndim (number of axes), size (total elements), dtype (data type).
  • Fast: vectorized operations avoid Python loops and use optimized C code.

Creating ndarrays (common methods)

import numpy as np
# from Python list
a = np.array([1, 2, 3])       # 1D
# ranges and spaced values
b = np.arange(0, 10, 2)       # [0 2 4 6 8]
c = np.linspace(0, 1, 5)      # 5 evenly spaced numbers
# special arrays
Z = np.zeros((3, 4))          # 3x4 zeros
O = np.ones((2, 2))
F = np.full((2,3), 7)         # filled with 7
# random
R = np.random.rand(3,3)       # 3x3 array of floats in [0,1)

Useful attributes and methods

  • arr.shape — tuple giving size per axis (e.g., (rows, cols)).
  • arr.ndim — number of dimensions (axes).
  • arr.size — total number of elements (product of shape).
  • arr.dtype — data type of elements.
  • arr.reshape(new_shape) — view with a new shape (compatible size).
  • arr.T — transpose (for 2D swaps axes).
  • arr.astype(dtype) — convert type.

Indexing and slicing

  • 1D: arr[i] retrieves element at index i.
  • 2D: arr[i, j] selects element at row i, column j.
  • Slice: arr[start:stop:step] works across axes: arr[1:4, :2].
  • Boolean indexing and fancy indexing (arrays of indices) let you select elements conditionally or by positions.

Vectorized arithmetic and broadcasting

Operations on ndarrays are elementwise by default: adding, subtracting, multiplying two arrays of the same shape applies the operation element-by-element. Broadcasting lets arrays with different shapes participate when shapes are compatible. Brief rule: align shapes from the trailing dimension; dimensions are compatible if they are equal or one of them is 1.

A = np.array([1,2,3])       # shape (3,)
B = np.array([[10],[20]])    # shape (2,1)
C = A + B                    # result shape (2,3) via broadcasting

Performance note
Because ndarrays store values in contiguous memory and use compiled code for operations, they are much faster and more memory-efficient than Python lists for numeric computations.

When to use ndarray
Any time you have numeric data where vectorized calculations, matrix math, or memory efficiency matter — e.g., data analysis, image processing, scientific computing, machine learning.

📌 Examples
  • 1D time series: store daily temperatures in a 1D ndarray and compute mean, min, max and rolling statistics quickly with vectorized operations.
  • 2D table: represent a spreadsheet of student marks as a 2D ndarray (shape: students x subjects) and compute column means (subject averages) using arr.mean(axis=0).
  • Image: a color image is a 3D ndarray with shape (height, width, channels). For an RGB image use shape like (480, 640, 3); image processing operations (brightness, filters) are vectorized.
  • Batch of images / video: a dataset of 100 images of size 32x32 RGB is a 4D ndarray with shape (100, 32, 32, 3).
  • Physics/sensor data: multi-channel sensor readings over time stored as a 2D array (time x channels) are convenient for filtering and plotting.
🧮 Formulas
  1. \[size = product(shape) # total elements\]
    \[e.g.\]
    \[size = m * n for shape (m\]
    \[n)\]
  2. \[elementwise: C = A op B => c_i = a_i op b_i (when shapes equal)\]
  3. \[broadcasting rule: compare shapes from trailing axes\]
    \[dimensions must be equal or one of them is 1\]
    \[result dimension is the max of the two\]
  4. \[matrix multiply (dot): (A dot B)[i\]
    \[j] = sum_k A[i\]
    \[k] * B[k\]
    \[j] # shapes: (m\]
    \[p) dot (p\]
    \[n) -> (m\]
    \[n)\]
  5. \[reshape constraint: new_shape must have the same product of dimensions as original shape\]
💻3

Array creation routines

💻 COMPUTER SCIENCE / IT

Array creation routines

Key Point: total_elements = product of dimensions = n1 * n2 * ... * nk (use arr.size)

Array creation routines are the NumPy functions you use to create ndarrays (NumPy arrays) with specific values, shapes and data types. They are the starting point for any numerical program because arrays store numeric data efficiently in contiguous memory and provide vectorized operations. Common routines include:

- np.array(obj, dtype=...): create an array from a Python list/tuple/sequence. Use dtype to set data type (int, float, bool, etc.).

- np.arange(start, stop, step): like Python range but returns an ndarray; useful for discrete sequences and time steps.

- np.linspace(start, stop, num): create num evenly spaced values between start and stop (inclusive). Good for plotting continuous functions.

- np.logspace(start_exp, stop_exp, num, base=10): values spaced evenly on a log scale.

- np.zeros(shape, dtype=...) and np.ones(shape, dtype=...): arrays filled with 0 or 1. Useful for initialization.

- np.empty(shape, dtype=...): allocate array without initializing entries (faster, values uninitialized).

- np.full(shape, fill_value, dtype=...): fill with a specific constant.

- np.eye(N) and np.identity(N): identity matrix (1s on diagonal, 0s elsewhere).

- np.diag(v): create a diagonal matrix from vector v or extract diagonal from matrix.

- np.fromiter(iterable, dtype, count=-1) and np.asarray(obj): build arrays from iterables or convert sequences to arrays without copying when possible.

Key attributes and concepts connected to creation routines: shape (tuple of dimension sizes), ndim (number of dimensions), dtype (type of elements), size (total elements), itemsize (bytes per element) and nbytes (total bytes = size * itemsize). Use .reshape(new_shape) to change shape without changing data order (if compatible).

Practical tips: use np.zeros or np.full for initialization, np.linspace for smooth plotting, and np.arange for step-based ranges. Prefer np.asarray when you want to avoid copying an existing array-like object; use copy() when you need an independent copy.

📌 Examples
  • import numpy as np # 1D array from list arr = np.array([10, 20, 30]) # dtype inferred as int
  • np.arange(0, 10, 2) # array([0, 2, 4, 6, 8]) — useful for time steps or indices
  • np.linspace(0, 1, 5) # array([0. , 0.25, 0.5 , 0.75, 1. ]) — good for plotting a smooth curve
  • np.zeros((2,3), dtype=float) # 2x3 matrix of zeros — useful to initialize accumulators
  • np.ones((3,3), dtype=int) # 3x3 matrix of ones
  • np.full((2,2), 7) # 2x2 array filled with 7 (e.g., a mask or constant grid)
🧮 Formulas
  1. \[total_elements = product of dimensions = n1 * n2 * ... * nk (use arr.size)\]
  2. \[memory_bytes = total_elements * itemsize (arr.nbytes == arr.size * arr.itemsize)\]
  3. \[linspace step = (stop - start) / (num - 1) (when num > 1\]
    \[endpoints included)\]
  4. \[arange count ≈ ceil((stop - start) / step) (depends on step and floating rounding)\]
  5. \[reshape constraint: product(new_shape) must equal total_elements\]
📊4

Data types (dtype)

💻 COMPUTER SCIENCE / IT

Data types (dtype)

Key Point: Memory usage (bytes) = number_of_elements × itemsize (where itemsize = dtype.itemsize).

What is dtype?
In NumPy, every array has a data type called dtype that describes the kind of elements it holds (integers, floats, booleans, complex numbers, fixed-length strings, objects, dates, etc.) and how they are stored in memory (number of bytes, byte order).

Why dtype matters
Dtype affects memory usage, speed and numerical behavior. Choosing an appropriate dtype can save memory and avoid unexpected precision loss or overflow.

Common NumPy dtypes

  • int8, int16, int32, int64 (signed integers)
  • uint8, uint16, uint32, uint64 (unsigned integers)
  • float16, float32, float64 (floating-point; float64 = double precision)
  • complex64, complex128 (complex numbers: real + imaginary)
  • bool (True/False)
  • str_/unicode_ or fixed-length string types
  • object (Python objects; flexible but slow and memory-heavy)
  • datetime64, timedelta64 (dates and durations)

Inspecting and setting dtype
Use the .dtype attribute to inspect and dtype=... argument or astype() to set or convert types.

import numpy as np
a = np.array([1, 2, 3])         # default integer dtype (platform dependent)
print(a.dtype)                   # e.g. int64
b = np.array([1.0, 2.0], dtype=np.float32)
print(b.dtype)                   # float32
c = a.astype(np.float64)         # convert to float64

Memory and performance
Each dtype has an itemsize (bytes per element). Memory used = number_of_elements × itemsize. Smaller dtypes use less memory and often run faster (cache-friendly), but may reduce precision or range.

Integer ranges (signed/unsigned)
A signed integer with n bytes stores values from -2^{8n-1} to 2^{8n-1}-1. An unsigned integer of n bytes stores from 0 to 2^{8n}-1.

Floating point basics
Floating types store numbers as sign × mantissa × 2^{exponent-bias}. More bytes → more exponent/mantissa bits → larger range and better precision (e.g. float32 vs float64).

Type promotion and rules
When combining arrays of different dtypes, NumPy promotes to a common dtype following rules (simple examples):

  • int + float → float
  • int/float + complex → complex
  • bool in arithmetic behaves like int (True→1, False→0)

Practical tips for Class 11 students

  • Use uint8 for images (0–255). Saves memory and is standard for pixel values.
  • Use float32 for large sensor datasets if float64 precision is not necessary.
  • Avoid object dtype unless you need mixed Python objects — it removes NumPy speed benefits.

Example code (quick summary)

import numpy as np
img = np.zeros((100,100), dtype=np.uint8)   # black image, small memory
temps = np.array([30.2, 29.8, 31.0], dtype=np.float32)
mask = temps > 30.0                        # boolean mask
print(mask.dtype)                            # bool
📌 Examples
  • Image pixels: grayscale images commonly use dtype=uint8 (values 0–255). Example: a 200×200 image of uint8 uses 200×200×1 bytes = 40,000 bytes (~39.06 KB).
  • Sensor data: temperature readings from a weather station stored as float32 to save memory while keeping fractional precision (e.g., 23.56°C).
  • Boolean masks: storing True/False values for filtering arrays uses dtype=bool (1 byte per element in NumPy). Example: mask = arr > 10
  • Complex signals: electrical engineering phasors or Fourier results can be stored in complex64/complex128 to keep real and imaginary parts together.
  • Mixed Python objects: lists of strings of different lengths or mixed types use dtype=object (flexible but slower and memory-heavy).
🧮 Formulas
  1. \[Memory usage (bytes) = number_of_elements × itemsize (where itemsize = dtype.itemsize).\]
  2. \[Signed integer range for n bytes: -2^(8n-1) to 2^(8n-1) - 1\]
    \[Example: int16 (n=2) range = -32768 to 32767.\]
  3. \[Unsigned integer range for n bytes: 0 to 2^(8n) - 1\]
    \[Example: uint8 (n=1) range = 0 to 255.\]
  4. \[Floating point representation (conceptual): value = (-1)^sign × (1.mantissa) × 2^(exponent - bias).\]
  5. \[Approximate precision of float with m mantissa bits ≈ 2^{-m} (machine epsilon ≈ 2^{-mantissa_bits}).\]
💻5

Array attributes and properties

💻 COMPUTER SCIENCE / IT

Array attributes and properties

Key Point: size = product(shape_i) (multiply all dimensions of shape)

Overview: In NumPy, an ndarray (N-dimensional array) stores homogeneous data in contiguous memory and exposes several attributes that describe its structure, type and memory layout. Understanding these attributes helps in debugging, optimizing performance and correctly manipulating arrays.

Primary attributes (attribute name — what it means):

  • ndim — number of dimensions (rank) of the array.
  • shape — tuple of array dimensions (length along each axis). For a 2×3 array shape is (2, 3).
  • size — total number of elements (product of shape entries).
  • dtype — data-type of the elements (e.g., int32, float64).
  • itemsize — size in bytes of each element (depends on dtype).
  • nbytes — total bytes consumed by the array data (equals size * itemsize).
  • strides — tuple of bytes to step in each dimension when traversing the array (used to compute memory offset).
  • T — shorthand for transpose (works for 2D; returns view with axes reversed for higher dims).
  • flags — object describing memory layout (C_CONTIGUOUS / F_CONTIGUOUS), writeable, aligned, etc.
  • base — if this array is a view, base points to the original array; otherwise None.
  • flat — 1-D iterator over elements.
  • real / imag — views for complex arrays components.

Important relationships and rules:

  • size == product(shape) — total number of entries equals product of dimensions.
  • nbytes == size * itemsize — total memory in bytes.
  • Element memory offset formula: offset_in_bytes = sum(index[k] * strides[k]) (for all axes k).
  • Reshape is allowed only if new_size == size (unless using copies/padding).
  • Views share the same base and memory; copies have base is None and separate memory.
  • Contiguity: C-order (row-major) means last index varies fastest; Fortran-order (column-major) means first index varies fastest. flags['C_CONTIGUOUS'] and flags['F_CONTIGUOUS'] indicate this.

Why these matter (practical consequences):

  • Performance: contiguous arrays (proper order and strides) allow faster loops and BLAS calls. Unusual strides can slow operations.
  • Memory: nbytes helps estimate memory usage; choose smaller dtypes to reduce footprint (e.g., float32 vs float64).
  • Correctness: ensuring shapes match when broadcasting or reshaping avoids runtime errors.
  • Views vs copies: modifying a view changes the underlying array; use copy() when isolation is needed.

Short code example (HTML-safe):

import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]], dtype='int32')
# a.ndim -> 2
# a.shape -> (2, 3)
# a.size -> 6
# a.dtype -> int32
# a.itemsize -> 4
# a.nbytes -> 24
# a.strides -> (12, 4)  # bytes to step in each axis for C-order

These attributes are read-only descriptors that let you inspect and reason about array layout and type.

📌 Examples
  • Example 1 — Basic attributes (1D): import numpy as np x = np.array([10, 20, 30], dtype='int16') print(x.ndim) # 1 print(x.shape) # (3,) print(x.size) # 3 print(x.dtype) # int16 print(x.itemsize) # 2 (bytes) print(x.nbytes) # 6 Example 2 — 2D array strides and transpose: import numpy as np A = np.arange(12).reshape(3, 4) print(A.shape) # (3, 4) print(A.strides) # (32, 8) if dtype=int64 on 64-bit (bytes for row, column step) B = A.T print(B.shape) # (4, 3) print(B.base is A) # True (transpose returns a view in many cases) Example 3 — view vs copy: import numpy as np orig = np.arange(6) view = orig.reshape(2, 3) # view shares memory copy = orig.reshape(2, 3).copy() # independent copy view[0,0] = 999 print(orig[0]) # changed to 999 copy[0,0] = -1 print(orig[0]) # still 999, copy change doesn't affect orig Example 4 — dtype and memory planning: import numpy as np big = np.zeros((10000, 10000), dtype='float64') print(big.nbytes) # large: 100M elements * 8 bytes = 800,000,000 bytes (~762 MB) # Use dtype='float32' to halve memory usage
🧮 Formulas
  1. \[size = product(shape_i) (multiply all dimensions of shape)\]
  2. \[nbytes = size * itemsize\]
  3. \[offset_in_bytes(index_0, ...\]
    \[index_{k}) = sum(index_k * strides[k])\]
  4. \[For reshape: new_size must equal original size (product of new shape equal to size)\]
  5. \[If dtype changes with astype without copy: new_itemsize will change and nbytes will be size * new_itemsize\]
💻6

Indexing and slicing

💻 COMPUTER SCIENCE / IT

Indexing and slicing

Key Point: 1-D indexing: arr[i] returns element at index i (0-based).

What are indexing and slicing?
Indexing is the way to access a single element of a NumPy array. Slicing is the technique to extract a range (subarray) from a NumPy array using a start:stop:step notation. These operations let you read, modify and extract parts of arrays efficiently.

Basic 1-D indexing
Use an integer index to get one element. Indexing is zero-based.

import numpy as np
a = np.array([10, 20, 30, 40])
val = a[2]   # val = 30

Negative indices
Negative indices count from the end: -1 is last element, -2 is second last, etc.

Basic slicing (1-D)
Syntax: arr[start:stop:step]

  • start — inclusive index where slice begins (default 0)
  • stop — exclusive index where slice ends (element at stop not included)
  • step — stride between elements (default 1)
Example: a[1:4] returns elements at indices 1,2,3.

Important: views vs copies
Slicing returns a view (not a copy) of the original array when possible. Modifying the slice can change the original array. Use slice.copy() to force a copy.

2-D (and higher) indexing and slicing
Use comma-separated indices for each axis: arr[row, col]. For slices, provide slice for each axis: arr[r1:r2, c1:c2].

m = np.arange(1,13).reshape(3,4)
# m = [[ 1,  2,  3,  4],
#      [ 5,  6,  7,  8],
#      [ 9, 10, 11, 12]]
sub = m[0:2, 1:3]   # rows 0..1, cols 1..2 -> [[2,3],[6,7]]
col2 = m[:, 2]      # all rows, column index 2 -> [3,7,11]

Fancy indexing and boolean indexing
- Fancy (integer array) indexing: use a list/array of indices: arr[[i,j,k]] creates a new array containing those items.
- Boolean masking: pass a boolean array of same shape to select elements where mask is True: arr[arr > 5].

Ellipsis and newaxis
- ... (ellipsis) can fill missing slice dimensions: useful in high-dimensional arrays.
- np.newaxis (or None) can add a new dimension: arr[:, np.newaxis] converts 1-D to column vector.

Practical tips
- Remember stop is exclusive.
- Negative step (e.g., arr[::-1]) reverses an array.
- Use copy() when you need an independent array.
- Fancy and boolean indexing return copies, not views.

Summary (short examples)

# 1-D slicing: a[1:5:2]
# 2-D slicing: m[0:2, 1:4]
# Boolean mask: a[a % 2 == 0]
# Fancy indexing: a[[0,3,4]]
📌 Examples
  • 1) 1-D indexing and slicing import numpy as np a = np.array([10, 20, 30, 40, 50]) print(a[0]) # 10 print(a[-1]) # 50 print(a[1:4]) # [20 30 40] print(a[::2]) # [10 30 50] (every 2nd element)
  • 2) 2-D submatrix and column selection import numpy as np m = np.arange(1,13).reshape(3,4) print(m) # [[ 1 2 3 4] # [ 5 6 7 8] # [ 9 10 11 12]] print(m[0:2, 1:3]) # [[2 3] [6 7]] print(m[:, 2]) # [3 7 11] (3rd column)
  • 3) Boolean indexing (select by condition) import numpy as np a = np.array([5, 12, 7, 20, 3]) mask = a > 8 print(mask) # [False True False True False] print(a[mask]) # [12 20]
  • 4) Fancy indexing (select arbitrary indices) import numpy as np a = np.array([10,20,30,40,50]) sel = a[[4,1,3]] print(sel) # [50 20 40]
  • 5) View vs copy demonstration import numpy as np a = np.arange(6) s = a[2:5] s[0] = 99 print(a) # a changed because s is a view -> [ 0 1 99 3 4 5] # use s = a[2:5].copy() to avoid modifying original
🧮 Formulas
  1. \[1-D indexing: arr[i] returns element at index i (0-based).\]
  2. \[1-D slicing: arr[start:stop:step] returns elements start..stop-1 with given stride\]
    \[Defaults: start=0\]
    \[stop=len(arr)\]
    \[step=1.\]
  3. \[Negative indices: arr[-1] is last element\]
    \[arr[-k] is k-th from end.\]
  4. \[Reverse: arr[::-1] reverses the array (step = -1).\]
  5. \[2-D indexing: arr[row\]
    \[col] selects element at (row\]
    \[col).\]
  6. \[2-D slicing: arr[rstart:rstop\]
    \[cstart:cstop] selects submatrix of rows rstart..rstop-1 and cols cstart..cstop-1.\]
💻7

Fancy indexing and boolean indexing

💻 COMPUTER SCIENCE / IT

Fancy indexing and boolean indexing

Key Point: 1D fancy indexing: result = arr[[i1, i2, ..., ik]]

Fancy indexing (also called integer-array indexing) means using integer arrays or lists to select arbitrary elements from a NumPy array. Example: arr[[0,2,4]] picks elements at indices 0, 2 and 4. Fancy indexing can be used in 1D and ND arrays. When multiple index arrays are provided, NumPy treats them elementwise: a[[0,1],[2,3]] selects elements (0,2) and (1,3). To select the Cartesian product of rows and columns use np.ix_(rows, cols).

Boolean indexing uses a boolean array (mask) of the same shape (or broadcastable to the array shape) to select elements where the mask is True. The mask is commonly created with a condition: mask = arr > value. Using arr[mask] returns a 1D array of selected values in row-major order.

Important behaviour notes:

  • Fancy indexing always returns a new array (a copy), not a view.
  • Boolean indexing also returns a 1D array of the selected elements (copy).
  • Slicing (e.g., arr[1:4]) typically returns a view, while fancy/boolean indexing return copies.
  • Mask shapes must match the array shape or be broadcastable; integer index arrays determine the shape of the result.

Small illustrative code (NumPy):

import numpy as np
arr = np.array([10,20,30,40,50])
# Fancy indexing
arr[[0,2,4]]         # -> array([10, 30, 50])
# Boolean indexing
mask = arr > 25
arr[mask]            # -> array([30, 40, 50])

# 2D example
A = np.arange(12).reshape(3,4)
rows = [0,2]
cols = [1,3]
A[np.ix_(rows,cols)]    # selects 2x2 block using Cartesian product
A[[0,1],[2,3]]         # selects elements (0,2) and (1,3) elementwise
📌 Examples
  • Select specific student marks: marks = np.array([55,78,92,46,81]); top_indices = [2,4]; marks[top_indices] -> [92, 81] (fancy indexing).
  • Filter students who passed: mask = marks >= 50; passed = marks[mask] -> selects all marks >=50 (boolean indexing).
  • Image processing: img is a HxWx3 array. mask = (img[:,:,0] > 200) & (img[:,:,1] < 50) selects pixels with strong red channel and low green; img[mask] gives the RGB values of those pixels.
  • Sensor data: timeseries arr of temperatures; remove bad readings: good = arr[(arr > -40) & (arr < 60)] returns only plausible temperature readings.
  • Selecting rows and columns: A[np.ix_([0,2],[1,3])] picks rows 0 & 2 and columns 1 & 3 as a 2x2 submatrix (Cartesian product selection).
🧮 Formulas
  1. \[1D fancy indexing: result = arr[[i1\]
    \[i2, ...\]
    \[ik]]\]
  2. \[ND elementwise fancy indexing: result = A[[r1\]
    \[r2, ...], [c1\]
    \[c2, ...]] # pairs (r1,c1)\]
    \[(r2,c2), ...\]
  3. \[Cartesian product selection: result = A[np.ix_(row_indices\]
    \[col_indices)]\]
  4. \[Boolean mask creation: mask = condition_on_array (e.g.\]
    \[mask = arr > value)\]
  5. \[Boolean indexing: selected = arr[mask] # returns 1D array of True positions\]
  6. \[Combine masks: mask = (arr &gt\]
    \[a) &amp\]
    \[(arr &lt\]
    \[b) # use & and | with parentheses\]
💻8

Broadcasting

💻 COMPUTER SCIENCE / IT

Broadcasting

Key Point: Given shapes A: (a1, a2, ..., an) and B: (b1, b2, ..., bm), align from the right. For each paired dimension (ai, bj): compatible if ai == bj or ai == 1 or bj == 1.

What is broadcasting?

Broadcasting is a set of rules NumPy follows to perform arithmetic operations on arrays of different shapes. Instead of explicitly copying data to match shapes, NumPy virtually stretches arrays with smaller dimensions so that elementwise operations can proceed efficiently.

Key rules (how broadcasting works)

  • Compare shapes from the trailing (rightmost) dimensions.
  • Two dimensions are compatible if they are equal or one of them is 1.
  • If a dimension is 1 in one array, that array is conceptually repeated along that axis to match the other array.
  • If all paired dimensions are compatible, the result shape has each dimension = max(size1, size2) for that axis.
  • If any paired dimensions are incompatible (neither equal nor 1), NumPy raises a ValueError.

Why it matters

  • Broadcasting enables concise, vectorized code (no Python loops).
  • It is memory-efficient because values are not physically copied — repetition is virtual.
  • Understanding broadcasting prevents subtle bugs (unexpected shape expansion or errors).

Common patterns

  • Scalar + array: scalar is treated as an array of shape () and broadcasts to the array's shape.
  • 1-D vs 2-D: a 1-D array can broadcast across rows or columns of a 2-D array if shapes align or you explicitly add an axis using np.newaxis or reshape.
  • Column vector (n,1) + row vector (1,m) → result (n,m).

Tips: Use np.broadcast_to to see a broadcasted view, and np.expand_dims or [:, None] to add an axis intentionally when aligning shapes.

📌 Examples
  • Scalar + 1D array: import numpy as np A = np.array([1, 2, 3]) B = 5 A + B # -> array([6, 7, 8]) 1 is treated as shape () and broadcasts to shape (3,).
  • 1D + 2D (row across each row): import numpy as np A = np.array([[10, 20, 30], [40, 50, 60]]) # shape (2, 3) B = np.array([1, 2, 3]) # shape (3,) A + B # B broadcasts to shape (2,3) -> result [[11,22,33],[41,52,63]]
  • Column vector + row vector -> outer grid: import numpy as np C = np.array([[1], [2], [3]]) # shape (3,1) D = np.array([[10, 20, 30]]) # shape (1,3) C + D # result shape (3,3): [[11,21,31],[12,22,32],[13,23,33]]
  • Using newaxis to align shapes: import numpy as np v = np.array([1,2,3]) # shape (3,) M = np.array([[10],[20]]) # shape (2,1) # Make v a row vector (1,3) and add to M (2,1) result = M + v[np.newaxis, :] # shapes (2,1) and (1,3) -> (2,3)
  • Incompatible shapes raise an error: import numpy as np A = np.zeros((2,3)) B = np.ones((3,2)) A + B # ValueError: operands could not be broadcast together with shapes (2,3) (3,2)
🧮 Formulas
  1. \[Given shapes A: (a1\]
    \[a2, ...\]
    \[an) and B: (b1\]
    \[b2, ...\]
    \[bm)\]
    \[align from the right\]
    \[For each paired dimension (ai\]
    \[bj): compatible if ai == bj or ai == 1 or bj == 1.\]
  2. \[Resulting shape dimension at each axis = max(ai\]
    \[bj) (after right alignment and treating missing dims as 1).\]
  3. \[Example: A shape (3,1) and B shape (1,4) -> result shape = (3,4) because max(3,1)=3 and max(1,4)=4.\]
💻9

Universal functions (ufuncs)

📐 MATHEMATICAL FORMULA / THEOREM

Universal functions (ufuncs)

Key Point: Unary ufunc (element-wise): B[i] = f(A[i]) for all indices i

What are ufuncs?
Universal functions (ufuncs) in NumPy are functions that operate element-wise on ndarrays. A ufunc takes one or more arrays as input and returns an array where the function has been applied to each element. They are implemented in C, so they are fast and memory-efficient compared to Python loops.

Why use ufuncs?

  • Element-wise operations: apply mathematical operations to every element of an array with a single call.
  • Vectorized code: simpler, clearer and much faster than explicit Python loops.
  • Broadcasting support: operate on arrays of different shapes when compatible.

Types of ufuncs

  • Unary ufuncs (one input): e.g. np.sqrt, np.exp, np.log, np.sin. They map each element x to f(x).
  • Binary ufuncs (two inputs): e.g. np.add, np.subtract, np.multiply, np.divide, np.maximum. They map element pairs (x,y) to g(x,y).

Common ufunc methods

  • ufunc.reduce(array): combines elements using the ufunc (e.g. np.add.reduce is sum).
  • ufunc.accumulate(array): cumulative application (e.g. np.add.accumulate is cumsum).
  • ufunc.outer(A, B): computes the outer operation producing a matrix of pairwise results.

Example (concept)

import numpy as np
a = np.array([1, 4, 9])
# Unary ufunc
print(np.sqrt(a))      # array([1., 2., 3.])
# Binary ufunc with broadcasting
print(np.add(a, 5))    # array([ 6,  9, 14])
# Reduce
print(np.add.reduce(a))  # 14

Performance note
Because ufuncs are vectorized and implemented in compiled code, they run orders of magnitude faster than element-by-element Python loops for large arrays.

📌 Examples
  • Compute element-wise square root of sensor readings: readings = np.array([25, 36, 49]); np.sqrt(readings) -> array([5.,6.,7.])
  • Add a calibration offset to all measurements: offsets = 2; corrected = np.add(measurements, offsets) (broadcasts offset to every element)
  • Apply trigonometric transform to signal samples: t = np.linspace(0, 2*np.pi, 100); signal = np.sin(t) # uses np.sin (unary ufunc)
  • Compute pairwise products using outer: A = np.array([1,2,3]); B = np.array([10,20]); np.multiply.outer(A,B) -> [[10,20],[20,40],[30,60]]
  • Cumulative sum with ufunc accumulate: data = np.array([1,2,3,4]); np.add.accumulate(data) -> array([1,3,6,10])
🧮 Formulas
  1. \[Unary ufunc (element-wise): B[i] = f(A[i]) for all indices i\]
  2. \[Binary ufunc (element-wise): C[i] = g(A[i]\]
    \[B[i]) where A and B are same-shape or broadcastable\]
  3. \[Reduce (example with add): s = np.add.reduce(A) = sum_{i} A[i]\]
  4. \[Accumulate (example with add): r[k] = sum_{i=0..k} A[i] (np.add.accumulate)\]
  5. \[Outer (example with multiply): M[i,j] = A[i] * B[j] (np.multiply.outer)\]
⚖️10

Arithmetic and vectorized operations

💻 COMPUTER SCIENCE / IT

Arithmetic and vectorized operations

Key Point: Elementwise add/subtract/multiply/divide: C[i] = A[i] ±/*/÷ B[i] for arrays A, B of same shape.

What it is
Arithmetic and vectorized operations in NumPy mean performing mathematical operations on whole arrays (vectors, matrices, tensors) at once instead of element-by-element with explicit Python loops. NumPy implements these operations in optimized C code, so they are concise and very fast.

Elementwise arithmetic
When two arrays have the same shape, operations are applied element by element. For arrays A and B of shape (n,), A + B produces an array C where C[i] = A[i] + B[i]. NumPy supports all common arithmetic operators: +, -, *, /, // (floor divide), % (modulus), ** (power).

Scalar operations
A scalar can be combined with an array: every element is operated with the scalar. Example: arr * 2 doubles every element in arr.

Broadcasting (shape compatibility)
Broadcasting allows arrays of different shapes to participate in elementwise operations when one array can be 'stretched' along dimensions of size 1. Rules (brief):

  • Compare shapes from right to left.
  • Dimensions are compatible if they are equal or one of them is 1.
  • If compatible, the smaller array behaves as if repeated along the dimension with size 1.
Example: adding a (3,4) array and a (4,) row vector adds the row vector to every row.

Vectorized functions
NumPy and related modules provide vectorized math functions (no explicit loops): np.sin, np.cos, np.exp, np.sqrt, np.log, and aggregation/reduction functions like np.sum, np.mean, np.cumsum. These operate elementwise or reduce dimensions efficiently.

Linear-algebraic vectorized ops
Besides elementwise ops, there are vectorized linear algebra operations such as dot product (np.dot or @) and matrix multiplication. Dot product of two 1-D arrays a and b: np.dot(a,b) = sum(a[i]*b[i]).

Why use vectorized operations?

  • Performance: C-level loops make operations much faster than Python loops.
  • Concise code: expressions are shorter and clearer (for example, normalize data with one line).
  • Less error-prone: fewer explicit loops and index manipulations.

Short code examples

import numpy as np
# elementwise operations
A = np.array([1, 2, 3])
B = np.array([10, 20, 30])
C = A + B        # array([11, 22, 33])
D = A * 2        # array([2, 4, 6])

# broadcasting (add row vector to each row of a matrix)
M = np.array([[1,2,3],[4,5,6]])   # shape (2,3)
v = np.array([10,20,30])          # shape (3,)
R = M + v                          # each row of M increased by v

# vectorized math
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)                     # compute sine for 100 points at once

# dot product
s = np.dot(A, B)                   # 1*10 + 2*20 + 3*30 = 140

# aggregation
total = np.sum(B)                  # 60

📌 Examples
  • Image brightness adjustment: multiply a 2D/3D pixel array by a scalar to increase/decrease brightness (broadcasting applies when scalar or 1D channel array is used).
  • Sensor calibration: convert raw readings to physical units with vectorized formula calibrated = scale * raw + offset applied to entire recorded array.
  • Finance: compute daily returns for many stocks at once: returns = (prices[:,1:] - prices[:,:-1]) / prices[:,:-1] (elementwise division and subtraction).
  • Physics: elementwise operations on position arrays to compute velocity = (pos2 - pos1) / dt for many particles in parallel.
  • Statistics: normalize dataset features with vectorized formula z = (X - mean) / std where mean and std are computed per column and broadcast across rows.
  • Batch transformations: add a 1D translation vector to every row of a 2D array of 2D points using broadcasting.
🧮 Formulas
  1. \[Elementwise add/subtract/multiply/divide: C[i] = A[i] ±/*/÷ B[i] for arrays A\]
    \[B of same shape.\]
  2. \[Scalar operation: B[i] = a * A[i]\]
    \[where a is scalar.\]
  3. \[Broadcasting condition: two dimensions are compatible when they are equal or one of them is 1\]
    \[Shapes are aligned from right to left.\]
  4. \[Dot product (inner product): s = Σ_i (a_i * b_i) = np.dot(a\]
    \[b).\]
  5. \[Linear transform (vectorized): y = m * x + c applied to whole array x.\]
  6. \[Reduction (sum): sum(A) = Σ_i A[i]\]
    \[cumulative sum: cumsum(A)[k] = Σ_{i=0..k} A[i].\]
📊11

Aggregation and statistical functions

📐 MATHEMATICAL FORMULA / THEOREM

Aggregation and statistical functions

Key Point: Mean (arithmetic): mean = (1/N) * Σ xi

What this topic covers

Aggregation and statistical functions in NumPy provide fast ways to compute summary values from arrays: totals, averages, spread, percentiles, running sums, counts, and indices of extrema. These functions are optimized for performance and can operate along specific axes of multi-dimensional arrays.

Key concepts

  • Aggregation — reducing an array to fewer values (e.g., sum, product, min, max) using functions such as np.sum, np.prod, np.min, np.max.
  • Statistical — computing mean, median, variance, standard deviation, percentiles with np.mean, np.median, np.var, np.std, np.percentile.
  • Axis parameter — for multi-dimensional arrays use axis=0 to aggregate down the rows (column-wise) and axis=1 to aggregate across columns (row-wise). If axis=None (default) the function reduces the entire array to a scalar.
  • Indices of extremanp.argmax and np.argmin return positions (indices) of maximum and minimum values.
  • Running/cumulativenp.cumsum and np.cumprod compute cumulative sums/products.
  • Missing values — many functions have NaN-safe versions like np.nansum, np.nanmean that ignore NaN values.

Behavior and important options

  • dtype can be specified to control accumulator precision (e.g., sum of ints into a 64-bit int or float).
  • Variance and standard deviation by default compute population values: np.var(...) uses divisor N. For sample variance use ddof=1 (divisor N - ddof).
  • keepdims=True preserves reduced axes as length-1 dimensions, useful when broadcasting results back to original shape.
  • np.unique(arr, return_counts=True) gives unique values and their frequencies — useful for categorical aggregation.

Small code examples (illustrative)

import numpy as np
marks = np.array([72, 85, 91, 67, 88])
np.sum(marks)            # total marks
np.mean(marks)           # average
np.median(marks)         # median
np.std(marks)            # population std dev
np.var(marks, ddof=1)    # sample variance (ddof=1)
np.percentile(marks, 75) # 75th percentile
np.argmax(marks)         # index of highest mark
np.cumsum(marks)         # running total

# 2D array example
arr = np.array([[10,20,30],[40,50,60]])
np.sum(arr, axis=0)  # column-wise sums -> [50,70,90]
np.sum(arr, axis=1)  # row-wise sums    -> [60,150]

Why use NumPy functions?

  • They are vectorized and implemented in C, so they are much faster than Python loops for large datasets.
  • They provide concise, readable code for common summarisation tasks used in data analysis, science and machine learning.
📌 Examples
  • Student marks: Use np.mean to get class average, np.median to find middle score, np.std to measure spread. Example: np.mean([72,85,91,67,88]) -> 80.6
  • Daily sales: daily = np.array([1200,1500,900,1100,1700]). Use np.cumsum(daily) to plot cumulative sales across days; np.argmax(daily) finds day with maximum sales.
  • Sensor readings with missing data: readings = np.array([1.2, np.nan, 3.4, 2.1]). Use np.nanmean(readings) to compute mean ignoring NaN.
  • 2D exam scores: scores shape (students, subjects). Column-wise mean np.mean(scores, axis=0) gives average per subject; row-wise mean np.mean(scores, axis=1) gives average per student.
  • Categorical counts: categories = np.array(['A','B','A','C','B']); np.unique(categories, return_counts=True) -> (['A','B','C'], [2,2,1])
🧮 Formulas
  1. \[Mean (arithmetic): mean = (1/N) * Σ xi\]
  2. \[Population variance: var = (1/N) * Σ (xi - mean)^2\]
  3. \[Sample variance (unbiased\]
    \[ddof=1): s^2 = (1/(N-1)) * Σ (xi - mean)^2 [use np.var(...\]
    \[ddof=1)]\]
  4. \[Standard deviation: std = sqrt(variance)\]
  5. \[Median: value separating the higher half from the lower half (50th percentile)\]
  6. \[p-th percentile: the value below which p percent of observations lie\]
⚖️12

Axis concept and operations along axes

💻 COMPUTER SCIENCE / IT

Axis concept and operations along axes

Key Point: For a 2D array A with shape (m, n): Sum over axis 0 (columns): B_j = sum_{i=0}^{m-1} A_{i,j} -> B has shape (n,)

What is an axis? In NumPy, an axis is a direction along which elements are arranged in an array. Each dimension of an array has an axis number: axis 0 is the first dimension, axis 1 the second, and so on. A 1D array has one axis, a 2D array has two axes (rows = axis 0, columns = axis 1), a 3D array has three axes (for example depth, height, width or batch, height, width), etc.

Axis numbering and shapes: If an array has shape (d0, d1, d2, ...), then

  • axis 0 has length d0, axis 1 has length d1, etc.
  • Negative axis indices count from the end: axis -1 is the last axis.

Operations along axes mean the operation is applied across elements in the specified direction, typically reducing that axis (e.g., sum, mean) or combining arrays along that axis (e.g., concatenate). Common behaviors:

  • Reduction functions (sum, mean, min, max, std, var, argmax, argmin, etc.) collapse the specified axis, producing an output whose shape has that axis removed (unless keepdims=True).
  • keepdims=True preserves the reduced axis as a length-1 dimension (useful for broadcasting).
  • Transformation functions (transpose, swapaxes, moveaxis) reorder axes without changing data values, only their interpretation.
  • apply_along_axis lets you apply a Python function to 1D slices along a specified axis.

How to interpret axis in 2D (most common):

  • sum(axis=0) — collapse rows: you get one result per column (summing down each column).
  • sum(axis=1) — collapse columns: you get one result per row (summing across each row).

Examples of practical interpretation:

  • Spreadsheet: rows = records (students), columns = features (subjects). mean(axis=1) gives each student’s average; mean(axis=0) gives class average per subject.
  • Images: a grayscale image is 2D (height, width). An RGB image is 3D (height, width, channels). mean(axis=(0,1)) of an RGB image computes the average color across pixels (reduce the height and width axes, leaving channels).
  • Machine learning batch: array shape (batch, features). mean(axis=0) computes feature-wise mean across the batch; mean(axis=1) gives per-sample mean.

Important functions and behaviors:

  • np.sum, np.mean, np.min, np.max, np.std, np.var, np.cumsum, np.cumprod accept an axis argument.
  • np.concatenate(arrays, axis=...) joins arrays along the given axis. np.stack adds a new axis.
  • np.transpose or arr.T reorders axes; transpose accepts an axes tuple to specify the new order.
  • np.apply_along_axis(func, axis, arr) runs func on 1D slices along axis and collects results.

📌 Examples
  • Example 1 (2D sums): import numpy as np arr = np.array([[1, 2, 3], [4, 5, 6]]) arr.sum(axis=0) # output: array([5, 7, 9]) -> sums of each column arr.sum(axis=1) # output: array([6, 15]) -> sums of each row
  • Example 2 (image channel mean): # RGB image shape (height, width, 3) img = np.random.randint(0, 256, size=(100, 200, 3)) channel_mean = img.mean(axis=(0,1)) # output shape (3,) -> average R,G,B values
  • Example 3 (batch of vectors): batch = np.array([[0.5, 1.0, 0.0], [0.6, 0.8, 0.2], [0.4, 0.9, 0.1]]) # shape (3,3): (batch, features) feature_mean = batch.mean(axis=0) # mean per feature across batch sample_mean = batch.mean(axis=1) # mean per sample
  • Example 4 (keepdims and broadcasting): arr = np.array([[1,2],[3,4]]) s = arr.sum(axis=1, keepdims=True) # shape (2,1): [[3],[7]] # can broadcast: arr / s divides each row by its row-sum
🧮 Formulas
  1. \[For a 2D array A with shape (m\]
    \[n): Sum over axis 0 (columns): B_j = sum_{i=0}^{m-1} A_{i,j} -> B has shape (n,)\]
  2. \[Sum over axis 1 (rows): C_i = sum_{j=0}^{n-1} A_{i,j} -> C has shape (m,)\]
  3. \[General shape change on reduction: If A.shape = (d0\]
    \[d1, ...\]
    \[dk) and you reduce axis p\]
    \[result shape = (d0, ...\]
    \[d_{p-1}\]
    \[d_{p+1}, ...\]
    \[dk)\]
    \[If keepdims=True then dp becomes 1 instead of being removed.\]
  4. \[Concatenate: If X has shape (...\]
    \[a, ...)\]
    \[Y has matching shapes except axis p where X has length a and Y has length b\]
    \[then np.concatenate([X,Y]\]
    \[axis=p) has length a+b along axis p.\]
  5. \[Transpose (reorder axes): If perm is a permutation of (0..k)\]
    \[arr.transpose(perm) returns array with axes permuted\]
    \[new_shape[i] = old_shape[perm[i]].\]
💻13

Reshaping and transposing

💻 COMPUTER SCIENCE / IT

Reshaping and transposing

Key Point: Total elements: N = \prod_{i=0}^{k-1} shape[i] (product of all dimensions)

What they are

Reshaping and transposing are array-manipulation operations in NumPy that change how data is viewed without changing the underlying elements (when possible).

Reshaping

Reshaping changes the shape (dimensions) of an array while keeping the same number of elements. For example, a 1D array of length 12 can be reshaped into shapes like (3,4), (4,3), (2,2,3), etc. The only requirement is that the product of the new dimensions equals the total number of elements.

arr = np.arange(12)         # shape: (12,)
arr.reshape(3, 4)           # shape: (3, 4)
arr.reshape(-1, 6)          # infer first axis -> shape: (2, 6)

Notes:

  • Using -1 lets NumPy infer that dimension.
  • reshape usually returns a view (no copy) when possible; otherwise it returns a copy.
  • flatten() returns a copy; ravel() returns a view when possible (so ravel is generally preferred for efficiency).
  • np.resize differs because it can change total size (it repeats or trims elements) and modifies in place for ndarray.resize.

Transposing

Transposing reorders axes. For 2D arrays it swaps rows and columns: a shape (m, n) becomes (n, m). For higher-dimensional arrays you can provide a permutation of axes.

mat = np.array([[1,2,3], [4,5,6]])   # shape: (2, 3)
mat.T                                  # shape: (3, 2)
np.transpose(arr, axes=(2, 0, 1))      # permute axes for 3D array

Notes:

  • Transpose is very common when converting between frameworks that expect different axis orders (example: image arrays between HWC and CHW).
  • transpose typically returns a view by changing strides, so it's efficient.

Memory order and copies

NumPy arrays are stored in memory with a specific order (row-major 'C' by default or column-major 'F'). Some reshape or transpose operations can be implemented by only changing shape/strides (no copy). Others require rearranging data (copy) — especially when forcing a particular memory order with order='C' or order='F'.

When to use which

  • Reshape to convert flat feature vectors into matrices or image shapes for processing.
  • Transpose to swap axes (rows/columns), or reorder channels/axes between libraries.
  • Use ravel() when you want a flattened view without copying if possible; use flatten() when you explicitly want an independent copy.

Common pitfalls

  • Trying to reshape to incompatible shape (product mismatch) raises an error.
  • Assuming reshape always returns a copy — it may be a view, so modifying the result can affect the original array (and vice versa).
  • Confusing axis order in images: HWC (height, width, channels) vs CHW (channels, height, width).

Short reference

  • np.reshape(a, newshape, order='C')
  • a.reshape(newshape)
  • a.ravel(order='C') — flattened view when possible
  • a.flatten(order='C') — flattened copy
  • np.transpose(a, axes=None) or a.T
  • a.swapaxes(i, j) — swap two axes
📌 Examples
  • 1) Reshape 1D to 2D Code: import numpy as np arr = np.arange(12) arr2 = arr.reshape(3, 4) # result: array([[0,1,2,3],[4,5,6,7],[8,9,10,11]]) Explanation: 12 elements reorganized into 3 rows and 4 columns.
  • 2) Infer one axis with -1 Code: x = np.arange(24) y = x.reshape(-1, 6) # shape (4, 6) because 24/6 = 4 Explanation: -1 tells NumPy to compute that dimension automatically.
  • 3) Flatten vs ravel Code: a = np.array([[1,2],[3,4]]) flat_copy = a.flatten() flat_view = a.ravel() flat_view[0] = 100 # may modify 'a' because ravel returned a view Explanation: flatten() always copies; ravel() returns a view when possible.
  • 4) Transpose a matrix Code: m = np.array([[1,2,3],[4,5,6]]) mt = m.T # shape (3,2): array([[1,4],[2,5],[3,6]]) Explanation: rows become columns and vice versa.
  • 5) Reordering image axes (real-life ML example) Code: img_hwc = np.zeros((224, 224, 3)) # HWC format img_chw = np.transpose(img_hwc, (2, 0, 1)) # becomes (3, 224, 224) for frameworks expecting CHW Explanation: many deep learning libraries expect channels-first input.
  • 6) Swap axes in a 3D tensor Code: t = np.zeros((2, 3, 4)) # swap axis 0 and 2 t_swapped = t.swapaxes(0, 2) # new shape (4, 3, 2) Explanation: swapaxes lets you exchange any two axes.
🧮 Formulas
  1. \[Total elements: N = \prod_{i=0}^{k-1} shape[i] (product of all dimensions)\]
  2. \[Reshape requirement: \prod(new_shape) == \prod(old_shape) (unless using operations that change size like np.resize)\]
  3. \[2D transpose shape: (m\]
    \[n) -> (n\]
    \[m)\]
  4. \[General transpose: new_shape[j] = old_shape[axes[j]] when using axes permutation\]
  5. \[Using -1: if new_shape contains -1 at position p\]
    \[then new_shape[p] = N / \prod_{i != p} new_shape[i]\]
👑14

Stacking and splitting arrays

💻 COMPUTER SCIENCE / IT

Stacking and splitting arrays

Key Point: concatenate: np.concatenate((A,B), axis=k) -> other axes must match; resulting shape along axis k is sum of sizes.

What it means
In NumPy, stacking combines multiple arrays into one bigger array along a chosen axis. Splitting divides an array into multiple smaller arrays along a chosen axis. These operations are fundamental when preparing, combining or partitioning datasets.

Common functions

  • np.concatenate((a, b, ...), axis=...) — general join along an axis; arrays must match in all other dimensions.
  • np.vstack((a, b, ...)) — vertical stack (like adding rows) — equivalent to concatenate(..., axis=0) for 2D arrays.
  • np.hstack((a, b, ...)) — horizontal stack (like adding columns) — equivalent to concatenate(..., axis=1) for 2D arrays.
  • np.stack((a, b, ...), axis=k) — joins arrays by creating a new axis at position k; shapes of inputs must be identical.
  • np.dstack((a, b, ...)) — stacks arrays along the third axis (depth), useful to combine 2D arrays into a 3D array.
  • np.column_stack((a, b, ...)) — stacks 1D arrays as columns into a 2D array.
  • np.split(array, indices_or_sections, axis=...) — splits into equal pieces (if possible) or at specified indices.
  • np.hsplit, np.vsplit — shortcuts to split horizontally/vertically. np.array_split allows unequal splits.

Shape rules (short)
When concatenating along axis i, all arrays must have the same shape for every axis except i. For stack, all input arrays must have identical shapes; the result gains one more axis.

Small code examples

# vertical stack (rows added)
import numpy as np
A = np.array([[1,2],[3,4]])   # shape (2,2)
B = np.array([[5,6]])         # shape (1,2)
np.vstack((A,B))              # shape (3,2)

# horizontal stack (columns added)
C = np.array([[7],[8],[9]])  # shape (3,1)
np.hstack((np.vstack((A,B)), C))  # shape (3,3)

# stack creating a new axis
X = np.array([1,2])  # shape (2,)
Y = np.array([3,4])  # shape (2,)
np.stack((X,Y), axis=0)  # shape (2,2)
np.stack((X,Y), axis=1)  # shape (2,2) but different layout

# split an array
M = np.arange(12).reshape(3,4)
np.hsplit(M, 2)    # splits into 2 arrays each with 2 columns
np.array_split(M, 4, axis=1)  # splits into 4 parts, some may be smaller

Errors to watch for
If shapes don't match along non-concatenation axes you will get a ValueError. When using np.split with an integer number of sections, the size along that axis must be divisible by the number of sections (use array_split if not).

When to use in real life

  • Merging rows from multiple CSV files into one dataset (use vstack/concatenate).
  • Adding new feature columns to a dataset (use hstack or column_stack).
  • Combining color channels (R, G, B) into an image array (use dstack).
  • Splitting data into train/validation/test sets (use array_split or manual indexing).

Summary
Stacking joins arrays (along rows, columns, depth or a new axis). Splitting cuts arrays into parts. Pick the appropriate function based on desired axis and whether inputs need identical shapes.

📌 Examples
  • Vertical stacking: A.shape=(2,2), B.shape=(1,2) -> np.vstack((A,B)) results in shape (3,2). Use to append new records (rows).
  • Horizontal stacking: A.shape=(3,2), C.shape=(3,1) -> np.hstack((A,C)) results in shape (3,3). Use to add new features (columns).
  • Stack with new axis: X.shape=(2,), Y.shape=(2,) -> np.stack((X,Y), axis=0) gives shape (2,2) and axis=0 separates arrays; axis choice changes layout.
  • Depth stack: R, G, B each shape (height, width) -> np.dstack((R,G,B)) yields an image array shape (height, width, 3).
  • Splitting: M.shape=(3,4) -> np.hsplit(M,2) yields two arrays each shape (3,2); np.array_split(M,3,axis=1) can yield uneven parts if needed.
🧮 Formulas
  1. \[concatenate: np.concatenate((A,B)\]
    \[axis=k) -> other axes must match\]
    \[resulting shape along axis k is sum of sizes.\]
  2. \[vstack: if A.shape=(m,n) and B.shape=(p,n) -> vstack -> (m+p\]
    \[n).\]
  3. \[hstack: if A.shape=(m,n) and B.shape=(m,k) -> hstack -> (m\]
    \[n+k).\]
  4. \[stack: if A.shape = B.shape = (s0\]
    \[s1, ...\]
    \[sN) then np.stack((A,B)\]
    \[axis=r) -> result shape has one more axis: (s0,...\]
    \[s_{r-1}, 2\]
    \[s_r, ...\]
    \[sN).\]
  5. \[dstack: stacks 2D arrays along axis=2: if A,B have shape (m,n) -> dstack -> (m,n,2).\]
  6. \[split: np.split(X\]
    \[n\]
    \[axis=k) requires dimension_size_along_axis % n == 0\]
    \[otherwise use np.array_split which allows unequal parts.\]
💻15

Copy vs view

💻 COMPUTER SCIENCE / IT

Copy vs view

Key Point: Memory size: memory_bytes = arr.size * arr.itemsize

What they are
In NumPy an array can either be an independent copy of data or a view that shares the same underlying memory as another array. A copy stores its own data block; changing it does not affect the original. A view is a different array object that references the same data buffer as the original; changes through the view change the original and vice versa.

How views are usually created
Slicing (e.g. a[1:5]), many reshaping operations (reshape, transpose when possible), and ravel() (when it can return a view) typically produce views. Views are memory-efficient because they avoid duplicating data.

How copies are usually created
Advanced/ fancy indexing (e.g. a[[0,2,5]]), boolean indexing, np.copy(a), a.copy(), flatten(), and many arithmetic operations produce copies.

Detecting whether you have a view

  • arr.base: if arr.base is not None, arr is a view (arr.base refers to the original array that owns the data).
  • np.shares_memory(a, b): returns True if two arrays share memory.

Memory relation and index mapping
Every ndarray has attributes .shape, .dtype, .strides and .size. The byte offset of an element with indices (i0, i1, ...) in the underlying buffer is computed as:

offset = i0*strides[0] + i1*strides[1] + ...

This is why views with different shapes/strides (for example, a transpose) can still reference the same memory.

When to use which
Use a view when you want a lightweight window into a large dataset (memory saving and faster). Use a copy when you need an independent array (safe modification, pass to functions that modify in-place).

Common gotchas

  • Assuming slicing returns a copy: slices usually return views, so modifying a slice may modify the original.
  • Assuming reshape always copies: reshape returns a view iff the new shape is compatible with the same memory layout; otherwise it copies.
  • flatten() always returns a copy, while ravel() returns a view when possible.
📌 Examples
  • 1) Slicing returns a view (modifying slice changes original): import numpy as np a = np.arange(6) b = a[2:5] # view b[0] = 100 # now a == [0, 1, 100, 3, 4, 5]
  • 2) Advanced indexing returns a copy (modifying copy doesn't change original): import numpy as np a = np.arange(6) b = a[[2,3,4]] # copy b[0] = 100 # a remains [0,1,2,3,4,5]
  • 3) Using np.copy to force an independent array: import numpy as np a = np.arange(6) b = a.copy() # guaranteed copy b[1] = -5 # a unchanged
  • 4) ravel vs flatten: import numpy as np A = np.arange(9).reshape(3,3) r = A.ravel() # view when possible f = A.flatten() # always a copy r[0] = 999 # A[0,0] becomes 999, but changing f does not affect A
  • 5) Check sharing and base: import numpy as np A = np.arange(12).reshape(3,4) S = A[:, 1:3] # slice -> view print(S.base is A) # True or not None print(np.shares_memory(A, S)) # True
🧮 Formulas
  1. \[Memory size: memory_bytes = arr.size * arr.itemsize\]
  2. \[Byte offset of element at indices (i0\]
    \[i1, ...\]
    \[in): offset = sum(ik * strides[k]) for k=0..n\]
  3. \[View detection: arr.base is not None -> arr is a view (shares data)\]
    \[np.shares_memory(a\]
    \[b) -> True if they share memory\]
  4. \[ravel vs flatten: ravel() returns a view when possible\]
    \[flatten() returns a copy\]
  5. \[Copy creation common cases: fancy indexing\]
    \[boolean indexing\]
    \[flatten()\]
    \[np.copy()\]
    \[many arithmetic ops -> results are copies\]
💻16

Sorting and searching

💻 COMPUTER SCIENCE / IT

Sorting and searching

Key Point: Time complexity of general comparison-based sort: O(n log n)

Overview: Sorting arranges elements of an array in a specified order (ascending or descending). Searching finds element(s) or index/indices that match a condition. In NumPy these operations are optimized for numeric arrays and large datasets.

Sorting in NumPy: NumPy provides several routines:

  • np.sort(array) – returns a sorted copy.
  • array.sort() – sorts the array in place.
  • np.argsort(array) – returns indices that would sort the array (useful to reorder related arrays).
  • np.lexsort((key1, key2, ...)) – sorts by multiple keys (stable, last key is primary).
  • np.partition(array, k) / np.argpartition(array, k) – partially sorts so that the k-th element is in place and all smaller elements are before it (useful for selection problems like top-k).
  • Sort types (kind parameter): 'quicksort', 'mergesort', 'heapsort' — choose depending on stability and performance needs; mergesort is stable.

Searching in NumPy:

  • np.where(condition) – returns indices where condition is True (linear scan).
  • np.nonzero(array) – indices of non-zero elements.
  • np.searchsorted(sorted_array, value) – finds insertion index using binary search (array must be sorted).
  • np.argmax(array) / np.argmin(array) – index of first maximum/minimum.
  • Boolean indexing (e.g., arr[arr > 5]) is used to select elements matching a condition.

Complexities & behaviour:

  • General sorting (Timsort/mergesort/quicksort variants): average time O(n log n).
  • Linear search / condition checks (np.where, boolean indexing): O(n).
  • Binary search (np.searchsorted on sorted arrays): O(log n).
  • Partial selection (np.partition): expected O(n) for selection of k-th element.

Practical notes: Use argsort when you need to reorder several related arrays by the same key (e.g., names and marks). Use searchsorted for repeated membership/insertion checks on large sorted arrays. Prefer vectorized NumPy operations over Python loops for speed.

📌 Examples
  • Sort a marks array: import numpy as np; marks = np.array([78, 92, 55, 68]); sorted_marks = np.sort(marks) # [55, 68, 78, 92]
  • Get indices that sort an array (to reorder related data): idx = np.argsort(marks); names_sorted = names[idx]
  • Find where condition holds: high = np.where(marks > 75)[0] # indices of students with marks > 75
  • Binary-search insertion index on a sorted array: pos = np.searchsorted(sorted_marks, 80) # position to insert 80 to keep sorted order
  • Top-3 scores using partition: top3 = marks[np.argpartition(marks, -3)[-3:]] # efficient selection without full sort
🧮 Formulas
  1. \[Time complexity of general comparison-based sort: O(n log n)\]
  2. \[Time complexity of linear search / condition scan (np.where\]
    \[boolean indexing): O(n)\]
  3. \[Time complexity of binary search (np.searchsorted on sorted data): O(log n)\]
  4. \[Partial selection (nth element / partition): expected O(n) to place k-th element correctly\]
🔣17

Linear algebra and matrix operations

📐 MATHEMATICAL FORMULA / THEOREM

Linear algebra and matrix operations

Key Point: Vector dot product: u · v = Σ_i u_i v_i

Overview: Linear algebra studies vectors and matrices and the rules for combining them. In the context of NumPy (a Python library), matrices are represented as arrays and NumPy provides fast functions for algebraic operations used in data science, image processing, physics and engineering.

Vectors and matrices: A vector is a 1-D array (e.g. [x1, x2, x3]). A matrix is a 2-D array with rows and columns. In NumPy you create them with np.array(). Matrices represent linear maps, systems of linear equations, images (pixel grids), adjacency in graphs, etc.

Basic operations:

  • Addition / subtraction: element-wise; matrices must have the same shape. NumPy: A + B.
  • Scalar multiplication: multiply every element by a number: c * A.
  • Dot product (vectors): sum of pairwise products: np.dot(u, v) or u @ v.
  • Matrix multiplication: combine rows of the left with columns of the right. NumPy: A @ B or np.matmul(A, B).
  • Transpose: flip rows and columns. NumPy: A.T.
  • Determinant & inverse: scalar value and matrix inverse (when determinant ≠ 0). NumPy: np.linalg.det(A), np.linalg.inv(A).
  • Rank: number of independent rows/columns: np.linalg.matrix_rank(A).
  • Solving linear systems: Solve Ax = b with np.linalg.solve(A, b) (stable and preferred over explicit inversion).

Key properties:

  • Associativity: (A B) C = A (B C).
  • Distributivity: A(B + C) = AB + AC.
  • Transpose of a product: (AB)^T = B^T A^T.
  • Inverse of a product: if A and B invertible, (AB)^{-1} = B^{-1} A^{-1}.
  • Identity matrix I: AI = IA = A. In NumPy: np.eye(n).

NumPy tips: use vectorized operations (array arithmetic and @) instead of Python loops for speed. For stability and performance, prefer np.linalg.solve to computing inverses when solving linear systems. For large problems use specialized libraries (SciPy, BLAS/LAPACK) which NumPy calls under the hood.

📌 Examples
  • Image processing: a grayscale image is a matrix of pixel intensities. Filters (blur, sharpen) are applied by matrix convolution and linear transforms.
  • 2D graphics transformations: rotation, scaling and shear are matrix multiplications applied to coordinate vectors. For example, rotating point (x,y) uses a 2×2 rotation matrix.
  • Solving linear equations in physics/chemistry: e.g., Kirchhoff’s circuit laws produce systems Ax = b; use np.linalg.solve(A, b) to find currents.
  • Finance: covariance matrices of asset returns help compute portfolio risk. Portfolio variance = w^T Σ w (quadratic form).
  • Network analysis: adjacency matrix of a graph encodes connections; powers of the adjacency matrix count paths between nodes.
🧮 Formulas
  1. \[Vector dot product: u · v = Σ_i u_i v_i\]
  2. \[Matrix multiplication (element): (AB)_{ij} = Σ_k A_{ik} B_{kj}\]
  3. \[2×2 determinant: det([ [a\]
    \[b], [c\]
    \[d] ]) = ad - bc\]
  4. \[2×2 inverse (if det ≠ 0): A^{-1} = (1/det) * [ [d, -b], [-c\]
    \[a] ]\]
  5. \[Transpose property: (AB)^T = B^T A^T\]
  6. \[Linear system solution: for invertible A\]
    \[x = A^{-1} b (use np.linalg.solve(A\]
    \[b) in practice)\]
💻18

File I/O with NumPy

💻 COMPUTER SCIENCE / IT

File I/O with NumPy

Key Point: numpy.savetxt(fname, X, fmt='% .18e', delimiter=' ', header='', footer='', comments='# ')

File I/O (input/output) with NumPy means reading arrays from files and writing arrays to files. It is essential for saving results, loading datasets (for example CSV records of student marks or sensor logs), and exchanging data between programs. NumPy provides functions for text formats (CSV and others) and efficient binary formats (.npy, .npz).

Key ideas:

  • Text I/O is human-readable (CSV, space-delimited) and interoperable but slower and may lose exact dtype information.
  • Binary I/O ('.npy' and '.npz') is fast, preserves dtype and array shape, and is preferred when working only with NumPy.

Common functions and examples:

  • numpy.savetxt — save 1D or 2D arrays to text files (CSV or other delimiters). Use parameters like delimiter, fmt, header, comments.
  • numpy.loadtxt — load arrays from text files when data has consistent numeric types.
  • numpy.genfromtxt — like loadtxt but handles missing values, mixed types and can convert columns independently.
  • numpy.save — save a single array to a binary '.npy' file (fast and exact).
  • numpy.savez / numpy.savez_compressed — save multiple arrays into a single '.npz' archive (uncompressed or compressed).
  • numpy.load — load arrays from '.npy' or '.npz' files; when loading text files use loadtxt or genfromtxt.
  • ndarray.tofile and numpy.fromfile — lower-level functions for raw binary/text. Use with caution (less portable for text, needs dtype and shape management).

Short code examples (Class 11 level):

# Save a 2D array of student marks to CSV
import numpy as np
marks = np.array([[101, 78, 85], [102, 92, 88], [103, 67, 74]])
# columns: roll, math, physics
np.savetxt('marks.csv', marks, delimiter=',', fmt='%d', header='roll,math,physics', comments='')

# Load the CSV back
data = np.loadtxt('marks.csv', delimiter=',', skiprows=1, dtype=int)
# data is a 2D array; data[:,1] gives math marks

# Save and load a binary .npy file
np.save('marks_array.npy', marks)
loaded = np.load('marks_array.npy')

# Save multiple arrays
a = np.arange(6).reshape(2,3)
b = np.array([10,20,30])
np.savez('two_arrays.npz', arr1=a, arr2=b)
z = np.load('two_arrays.npz')
# access with z['arr1'] and z['arr2']

# Using genfromtxt for missing values
# file 'temps.csv' with header 'time,temp' and some blank temps
temps = np.genfromtxt('temps.csv', delimiter=',', names=True, dtype=None, encoding='utf-8')

Tips and behaviour:

  • Use savetxt/loadtxt for CSV that contain purely numeric data. For mixed datatypes or missing values prefer genfromtxt or pandas for convenience.
  • Binary files (.npy/.npz) are faster and preserve dtype and shape exactly; use them when working within NumPy.
  • When using savetxt, set fmt appropriately (e.g., '%d' for integers, '%.2f' for 2-decimal floats) to control output format.
  • Use skiprows and usecols to ignore headers or select columns while loading.
  • For large data, compressed .npz reduces disk space but increases CPU for compression/decompression.

Common errors and how to fix them:

  • ValueError from loadtxt: occurs when text file has inconsistent columns or non-numeric entries — try genfromtxt or specify dtype.
  • Incorrect shapes after fromfile: fromfile reads a flat array; reshape explicitly using .reshape() with correct order and size.
📌 Examples
  • Classroom marks CSV: Save marks of students with np.savetxt('marks.csv', marks, delimiter=',', fmt='%d', header='roll,math,physics', comments='') and load with np.loadtxt('marks.csv', delimiter=',', skiprows=1, dtype=int).
  • Sensor time series: A sensor writes time,value pairs each second. Use np.loadtxt('sensor.csv', delimiter=',', skiprows=1) to load numeric arrays and plot a line graph of time vs value.
  • Large experiment arrays: Save intermediate NumPy arrays as binary using np.save('data_step1.npy', arr) for fast reload during analysis without re-computation.
  • Multiple arrays in one file: Save model parameters weights and biases together with np.savez_compressed('model.npz', weights=W, biases=b) and later load by z=np.load('model.npz'); W_loaded = z['weights'].
🧮 Formulas
  1. \[numpy.savetxt(fname\]
    \[X\]
    \[fmt='% .18e'\]
    \[delimiter=' '\]
    \[header=''\]
    \[footer=''\]
    \[comments='# ')\]
  2. \[numpy.loadtxt(fname\]
    \[dtype=float\]
    \[delimiter=None\]
    \[skiprows=0\]
    \[usecols=None\]
    \[unpack=False)\]
  3. \[numpy.genfromtxt(fname\]
    \[dtype=float\]
    \[delimiter=','\]
    \[names=True\]
    \[missing_values=''\]
    \[filling_values=None\]
    \[encoding='utf-8')\]
  4. \[numpy.save(file\]
    \[arr\]
    \[allow_pickle=True)\]
  5. \[numpy.savez(file, *args, **kwds) and numpy.savez_compressed(file, *args, **kwds)\]
  6. \[numpy.load(file\]
    \[mmap_mode=None) # loads .npy or .npz\]
    \[for .npz returns a dict-like object\]
⚖️19

Performance considerations

💻 COMPUTER SCIENCE / IT

Performance considerations

Key Point: Memory (bytes) = n * itemsize (example: bytes = number_of_elements * 8 for float64)

Overview: Performance considerations in NumPy are about writing code that runs fast and uses memory efficiently. NumPy is implemented in C and provides fast array operations, but to get the best performance you must use NumPy features (vectorization, ufuncs, broadcasting, in-place operations) and be aware of memory layout, dtype choice, and copying vs. views.

Key principles:

  • Vectorize computations: Replace Python loops with NumPy array operations (ufuncs). Although both approaches may be O(n) in complexity, NumPy's C-level loops have a much smaller constant factor, giving large speedups (often 10x–100x).
  • Use ufuncs and built-in routines: Functions like np.add, np.multiply, np.dot, np.linalg.inv call optimized C/Fortran code (possibly backed by BLAS/LAPACK) and are faster than manual implementations in Python.
  • Avoid unnecessary copies: Many NumPy operations return views (no copy) but some create copies. Copies cost time and memory. Use np.asarray to avoid copying if you're already working with arrays and check .flags['C_CONTIGUOUS'] for memory layout.
  • Prefer in-place operations: When safe, use operators like a += b to modify arrays in place and avoid allocating new arrays.
  • Choose appropriate dtype: Memory and bandwidth matter. Using float32 uses half the memory of float64 and can be faster when precision is sufficient. Memory footprint = n * itemsize.
  • Memory layout and alignment: C-order (row-major) contiguous arrays are faster for most NumPy operations. Non-contiguous arrays (slices, transposes) may force copies or slower access patterns.
  • Pre-allocate arrays: Repeatedly growing arrays (e.g., with np.concatenate in a loop) is slow because it reallocates and copies. Use np.empty or allocate full size upfront.
  • Use specialized tools for large data: For datasets larger than memory use np.memmap or out-of-core libraries; for heavy arithmetic consider numexpr, Numba, or MKL/OpenBLAS-tuned NumPy builds.

Simple code illustration:

# Slow: Python loop
import numpy as np
n = 10_000_000
x = np.arange(n)
y = np.empty(n)
for i in range(n):
    y[i] = x[i] * 2.0

# Fast: vectorized NumPy ufunc
x = np.arange(n, dtype=np.float64)
y = x * 2.0

# In-place (saves memory):
x *= 2.0  # modifies x, no new array

Memory and speed considerations:

  • Memory needed (bytes) = n * itemsize. Example: 1e7 elements of float64 (8 bytes) → 80 MB.
  • Time complexity examples: elementwise operations are O(n); naive matrix multiplication is O(n^3) for n x n matrices, but optimized BLAS implementations are much faster per operation.
  • Views vs copies: a slice like a[::2] returns a view if possible; operations that change shape or order (transpose of non-contiguous) may create a copy.

Practical checklist for faster NumPy code:

  1. Vectorize: avoid Python for-loops over array elements.
  2. Use np.dot, np.matmul, np.linalg functions for linear algebra.
  3. Prefer in-place operations when you do not need the original data.
  4. Keep arrays contiguous when possible; call .copy(order='C') if a contiguous copy will speed repeated operations.
  5. Use smaller dtype when acceptable (int32/float32).
  6. Pre-allocate arrays instead of growing them.
  7. Profile code (timeit, %timeit in Jupyter) to find bottlenecks.
📌 Examples
  • Image processing: applying a filter to each pixel. Using vectorized array operations (convolution via scipy/NumPy) is far faster than looping over pixels in Python.
  • Finance: computing daily returns for millions of price series. Using NumPy broadcasting and ufuncs computes returns for all series at once instead of nested Python loops.
  • Machine learning preprocessing: normalizing features by (X - mean)/std using vectorized operations; precomputing mean/std and using broadcasting avoids per-row Python loops.
  • Sensor data aggregation: computing running sums or windowed statistics using NumPy or specialized functions (np.cumsum, np.convolve) rather than manual loops.
  • Large matrix computations: using np.dot or np.linalg.solve leverages optimized BLAS/LAPACK and multi-threading instead of implementing matrix multiply in Python.
🧮 Formulas
  1. \[Memory (bytes) = n * itemsize (example: bytes = number_of_elements * 8 for float64)\]
  2. \[Time complexity: element-wise operations = O(n)\]
    \[matrix multiply (naive) = O(n^3) for n x n matrices\]
  3. \[Speedup factor = time_python_loop / time_numpy_vectorized (empirical — often 10 to 100+)\]
  4. \[Copy cost ≈ memory_size / memory_bandwidth (higher memory use → slower due to bandwidth limits)\]
💻20

Practical examples and applications

💻 COMPUTER SCIENCE / IT

Practical examples and applications

Key Point: mean = (1/n) Σ_i x_i

What NumPy is and why it matters
NumPy is a Python library for efficient numerical computing. It provides the ndarray (n-dimensional array) object, vectorized operations, broadcasting, and many mathematical functions. For Class 11 Informatics Practices, focus on using NumPy to represent data (lists of numbers, images, matrices) and perform fast calculations useful in real-life problems.

Key practical strengths

  • Vectorized operations: perform elementwise math on whole arrays without explicit Python loops (fast and concise).
  • Broadcasting: apply operations between arrays of different shapes when compatible.
  • Linear algebra and statistics: built-in functions for dot product, matrix multiplication, mean, median, std, eigenvalues, etc.
  • Memory efficiency: arrays store numbers compactly and allow bulk operations.

Common applications and short examples

1. Class/test statistics
Calculate mean, median and standard deviation of student scores to analyse class performance.

import numpy as np
scores = np.array([78, 82, 91, 65, 70])
mean = np.mean(scores)
median = np.median(scores)
std = np.std(scores)

2. Time series / Finance (returns)
Compute daily returns or moving averages for stock prices using vectorized differences.

prices = np.array([100, 103, 101, 105])
returns = (prices[1:] - prices[:-1]) / prices[:-1]  # percent change
moving_avg_3 = np.convolve(prices, np.ones(3)/3, mode='valid')

3. Linear algebra / Physics
Solve simultaneous linear equations or perform matrix multiplication for systems, transformations or circuits.

A = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])
x = np.linalg.solve(A, b)  # solves Ax = b

# matrix multiplication
C = A @ np.array([[1, 2], [3, 4]])

4. Image processing
An image is a 2D (grayscale) or 3D (RGB) array. NumPy enables filters, masks and simple transforms.

import matplotlib.pyplot as plt
img = plt.imread('photo.jpg')       # shape (H, W, 3)
gray = img.mean(axis=2)            # convert to grayscale
blur = (gray[:-1, :-1] + gray[1:, :-1] + gray[:-1, 1:] + gray[1:, 1:]) / 4  # simple block blur
plt.imshow(gray, cmap='gray')

5. Machine learning / Data science
Feature matrices and labels are handled as arrays; predictions are vectorized: y = X @ w + b. Batch operations make training algorithms fast.

X = np.array([[1, 2], [3, 4], [5, 6]])  # feature matrix (3 samples, 2 features)
w = np.array([0.5, -0.2])
b = 1.0
predictions = X @ w + b

Why these are useful in real life
NumPy is the backbone for data analysis, scientific computing, finance modelling, image and signal processing, simulations and many machine learning tasks. Its speed and expressiveness allow students to prototype real-world solutions quickly.

Tips for students

  • Prefer NumPy operations over Python loops for speed.
  • Use vectorized expressions and broadcasting to keep code simple.
  • Visualise results (histograms, line plots, heatmaps) to understand data before making conclusions.
📌 Examples
  • Compute class test statistics (mean, median, standard deviation) and plot a histogram of marks to see distribution.
  • Calculate daily returns and moving averages for a stock price time series, then plot a line chart of prices and returns.
  • Solve a system of linear equations (e.g., currents in a circuit) using np.linalg.solve.
  • Apply a simple blur or edge detection on an image by treating it as a NumPy array and using convolution or differences.
  • Compute predictions for a set of samples in a linear model: y = X @ w + b, and evaluate mean squared error.
  • Create simulations (roll many dice or Monte Carlo simulations) using np.random and aggregate results with vectorized ops.
🧮 Formulas
  1. \[mean = (1/n) Σ_i x_i\]
  2. \[median = middle value when data sorted (or average of two middle values if n is even)\]
  3. \[variance = (1/n) Σ_i (x_i - mean)^2\]
  4. \[standard deviation = sqrt(variance)\]
  5. \[dot product (vectors a,b): a · b = Σ_i a_i * b_i\]
  6. \[matrix multiplication C = A × B where C_{ij} = Σ_k A_{ik} * B_{kj}\]

Key Concepts

NumPy
A Python library for numerical computing providing efficient arrays and mathematical functions.
ndarray
The primary NumPy object: an N-dimensional homogeneous array of fixed-size items.
dtype
Data type of elements in an ndarray (e.g., int32, float64).
shape
Tuple describing the size of the array along each dimension.
ndim
Number of dimensions (axes) of an ndarray.
size
Total number of elements in an ndarray.
reshape
Change the shape of an array without changing its data (if possible).
arange
Create a 1-D array with evenly spaced values using start, stop, step.
linspace
Create an array of evenly spaced numbers over a specified interval.
zeros
Create an array filled with zeros of a given shape and dtype.
ones
Create an array filled with ones of a given shape and dtype.
eye
Create a 2-D identity matrix with ones on the diagonal.
random (np.random)
Module to generate random numbers and random arrays (uniform, normal, integers, etc.).
indexing
Accessing individual elements of an array using integer indices.
slicing
Extracting sub-arrays using start:stop:step notation along axes.
boolean indexing
Selecting elements using a boolean mask array of the same shape.
broadcasting
Rules that allow arithmetic between arrays of different shapes by expanding them virtually.
vectorization
Applying operations on whole arrays at once for speed instead of element-wise Python loops.
ufunc
Universal functions: fast element-wise functions provided by NumPy (e.g., add, sin, exp).
axis
Dimension along which an operation is performed (axis=0 rows, axis=1 columns for 2D).

Practice Questions

  1. State three advantages of using a NumPy ndarray over a Python list for numerical computation. / संख्यात्मक गणना के लिए Python सूची की तुलना में NumPy ndarray का उपयोग करने के तीन लाभ बताइए।
    Show answer

    NumPy arrays are faster because operations run in optimized C code, they are more memory-efficient as elements are stored in contiguous memory, and they support vectorized element-wise operations without explicit Python loops. / NumPy सरणियाँ तेज़ होती हैं क्योंकि संक्रियाएँ अनुकूलित C कोड में चलती हैं, वे अधिक मेमोरी-कुशल होती हैं क्योंकि तत्व सन्निहित मेमोरी में संग्रहीत होते हैं, और वे स्पष्ट Python लूप के बिना सदिशकृत (vectorized) तत्व-वार संक्रियाओं का समर्थन करती हैं।

  2. Define the attributes shape, ndim, size and dtype of an ndarray, and for an array of shape (2,3) of int32 give its size and nbytes. / ndarray के गुण shape, ndim, size और dtype को परिभाषित कीजिए, और int32 की (2,3) आकार वाली सरणी के लिए उसका size और nbytes दीजिए।
    Show answer

    shape is the tuple of dimensions, ndim is the number of dimensions, size is the total number of elements (product of shape), and dtype is the element data type. For shape (2,3) of int32, size = 6 and nbytes = size × itemsize = 6 × 4 = 24 bytes. / shape आयामों का टपल है, ndim आयामों की संख्या है, size कुल तत्वों की संख्या (shape का गुणनफल) है, और dtype तत्व डेटा प्रकार है। int32 की (2,3) आकार वाली सरणी के लिए size = 6 और nbytes = size × itemsize = 6 × 4 = 24 बाइट।

  3. State the broadcasting rule for two arrays and determine the result shape of adding an array of shape (3,1) to one of shape (1,4). / दो सरणियों के लिए ब्रॉडकास्टिंग नियम बताइए और आकार (3,1) की सरणी को आकार (1,4) की सरणी में जोड़ने पर परिणाम का आकार निर्धारित कीजिए।
    Show answer

    Comparing shapes from the trailing dimensions, two dimensions are compatible if they are equal or one of them is 1, and the result dimension is the maximum of the two. So (3,1) + (1,4) gives result shape (3,4). / आकारों की तुलना अंतिम आयामों से करते हुए, दो आयाम संगत होते हैं यदि वे बराबर हों या उनमें से एक 1 हो, और परिणाम आयाम दोनों में से अधिकतम होता है। अतः (3,1) + (1,4) से परिणाम आकार (3,4) मिलता है।

  4. What is the difference between a view and a copy in NumPy, and which one does basic slicing usually return? / NumPy में view और copy के बीच क्या अंतर है, और मूल स्लाइसिंग सामान्यतः कौन-सा लौटाती है?
    Show answer

    A view shares the same underlying memory as the original array so modifying it changes the original, while a copy has independent memory so changes do not affect the original. Basic slicing usually returns a view, whereas fancy and boolean indexing return copies. / view मूल सरणी के समान अंतर्निहित मेमोरी साझा करता है इसलिए इसे बदलने से मूल बदल जाता है, जबकि copy की स्वतंत्र मेमोरी होती है इसलिए परिवर्तन मूल को प्रभावित नहीं करते। मूल स्लाइसिंग सामान्यतः view लौटाती है, जबकि फैंसी और बूलियन इंडेक्सिंग copy लौटाती है।

  5. Given a = np.array([5, 12, 7, 20, 3]), write the boolean indexing expression to select values greater than 8 and state the result. / a = np.array([5, 12, 7, 20, 3]) दिया गया है, 8 से बड़े मानों को चुनने हेतु बूलियन इंडेक्सिंग व्यंजक लिखिए और परिणाम बताइए।
    Show answer

    a[a > 8] selects the elements where the mask is True, giving array([12, 20]). The mask a > 8 evaluates to [False, True, False, True, False]. / a[a > 8] उन तत्वों को चुनता है जहाँ मास्क True है, जो array([12, 20]) देता है। मास्क a > 8 का मान [False, True, False, True, False] होता है।

  6. What are universal functions (ufuncs)? Distinguish unary and binary ufuncs with one example each. / सार्वभौमिक फलन (ufuncs) क्या हैं? एक-एक उदाहरण के साथ एकल (unary) और द्विआधारी (binary) ufuncs में अंतर बताइए।
    Show answer

    ufuncs are NumPy functions that operate element-wise on arrays using fast compiled code. A unary ufunc takes one input, e.g., np.sqrt(a), while a binary ufunc takes two inputs, e.g., np.add(a, b). / ufuncs वे NumPy फलन हैं जो तीव्र संकलित कोड का उपयोग करके सरणियों पर तत्व-वार कार्य करते हैं। एकल ufunc एक इनपुट लेता है, जैसे np.sqrt(a), जबकि द्विआधारी ufunc दो इनपुट लेता है, जैसे np.add(a, b)।

  7. For a 2D array of student marks (rows = students, columns = subjects), write the NumPy expression to compute the average mark per subject and explain the axis used. / छात्र अंकों की 2D सरणी (पंक्तियाँ = छात्र, स्तंभ = विषय) के लिए, प्रति विषय औसत अंक निकालने हेतु NumPy व्यंजक लिखिए और प्रयुक्त axis समझाइए।
    Show answer

    arr.mean(axis=0) computes the mean down each column, giving the average for each subject. axis=0 collapses the rows (students) so the operation runs across students for every subject. / arr.mean(axis=0) प्रत्येक स्तंभ के नीचे माध्य निकालता है, जिससे प्रत्येक विषय का औसत मिलता है। axis=0 पंक्तियों (छात्रों) को समेटता है इसलिए संक्रिया प्रत्येक विषय के लिए छात्रों के आर-पार चलती है।

  8. Why is dtype=uint8 commonly used for image pixel data, and what range of values can it store? / छवि पिक्सेल डेटा के लिए dtype=uint8 सामान्यतः क्यों उपयोग किया जाता है, और यह किस परास के मान संग्रहीत कर सकता है?
    Show answer

    uint8 is used because pixel intensity values range from 0 to 255, which exactly fits an unsigned 8-bit integer, and it saves memory using only 1 byte per element. An unsigned 8-bit integer stores values from 0 to 2^8 − 1 = 255. / uint8 इसलिए उपयोग होता है क्योंकि पिक्सेल तीव्रता मान 0 से 255 तक होते हैं, जो ठीक एक अहस्ताक्षरित 8-बिट पूर्णांक में समाते हैं, और यह प्रति तत्व केवल 1 बाइट उपयोग करके मेमोरी बचाता है। एक अहस्ताक्षरित 8-बिट पूर्णांक 0 से 2^8 − 1 = 255 तक मान संग्रहीत करता है।

Related Laws & Principles

Explore all

Foundational laws & principles connected to this chapter — tap to open in the Laws Explorer.

Loading related laws…
Sourced from 273 content files · LLOS Learn · browse all chapters