Skip to main content
Chapters Covered

Numpy: Numerical Python

  • What is NumPy: A powerful open-source Python library used for numerical computing and scientific data processing.
  • Core Object (ndarray): Provides a high-performance N-dimensional array object along with a rich collection of mathematical functions to perform operations efficiently on large datasets.
  • Memory & Speed: Unlike Python lists, NumPy arrays are stored in contiguous memory and contain elements of the same data type, making them significantly faster and more memory-efficient.
  • Ecosystem Foundation: Forms the foundation of many popular data science and machine learning libraries such as Pandas, Matplotlib, SciPy, Scikit-learn, TensorFlow, and PyTorch.

1. Introduction

Approach for Learning NumPy

  • Concepts over Syntax: Focus on structural operations and array transformations; rely on official documentation for specific API signatures.
  • Interactive Experimentation: Run code side-by-side in a notebook or REPL to inspect array shapes, dimensions, and data types in real time.
  • Project-Driven Workflows: Solidify patterns and vectorization habits by building end-to-end data pipelines.

Installing and Running NumPy

  • Installation: In your terminal or a Jupyter notebook cell, install the package using pip:
  • Importing: It is standard practice to import NumPy under the alias np to keep code clean and standardised:
  • Interactive Environments: Using interactive environments like Jupyter notebooks allows you to execute blocks of code on-the-fly and check array shapes and dimensions immediately.

2. NumPy Fundamentals

Why NumPy Arrays? (Need and Internals)

Standard Python lists are great for general use, but they become slow and memory-heavy when working with millions of numbers. NumPy introduces the NDArray (N-Dimensional Array) to solve this:

1. Python Lists vs. NumPy Arrays (Memory & Speed)

  • Python Lists (Scattered Pointers): A list can hold mixed data types ([1, "hello", 3.14]). Because of this flexibility, each element is an independent object scattered across memory, and Python has to check each item’s type during loops.
  • NumPy Arrays (Tightly Packed Block): An array is homogeneous—every element shares the exact same data type (e.g., all integers). All values are stored side-by-side in one continuous block of RAM.

2. Key Advantages

  1. Memory Efficiency: No overhead from object wrappers or scattered pointers.
  2. Blistering C-Speed: Built on precompiled C code under the hood—loops run at hardware speed without Python interpreter overhead.
  3. Best of Both Worlds: You write simple, clean Python code while getting near C-level performance.

Element-by-Element (Element-Wise) Operations

Element-by-element (or element-wise) means that an operation is applied to matching pairs of elements at identical index coordinates independently (result[i] = a[i] op b[i]).
  • NumPy Arrays vs. Standard Python Lists:
    • In standard Python, + on lists concatenates them: [1, 2] + [3, 4] →\rightarrow [1, 2, 3, 4].
    • In NumPy, arithmetic operations perform element-by-element calculation across corresponding positions.

Vectorization

Vectorization is the technique of writing mathematical operations directly on entire arrays without writing explicit Python for loops or manual index tracking.
  • How it works: You write concise mathematical syntax (e.g., C = A * B), and NumPy pushes the loop execution down to precompiled, highly optimized C routines at hardware speed.

Overview of Broadcasting

Broadcasting is NumPy’s automatic way of expanding a smaller array (or a single scalar number) so that its shape matches a larger array for element-by-element operations.

How the Smaller Array Expands (Intermediate Step):

  1. Scalar to 1D Array Expansion: A single number stretches across every position of the array:
  2. 1D Row Vector to 2D Matrix Expansion: A single row stretches downward to duplicate itself for every row in the matrix:
Note: Under the hood, this expansion is virtual—NumPy performs the calculation directly without creating extra copies in memory.

3. Creating Arrays

Basic Array Creation

To instantiate a standard array from a Python list or iterable, use np.array():

Zeros Array (np.zeros)

Creates an array filled entirely with zeros.
  • Usage: Pass the desired shape as a single integer or a tuple.
  • Note: Do not pass multiple dimensions as flat arguments (e.g., np.zeros(2, 3)), as NumPy will attempt to interpret the second argument as a data type. Always wrap multi-dimensional shapes in an outer tuple.

Ones Array (np.ones)

Creates an array filled entirely with ones, acting similarly to np.zeros.

Empty Array (np.empty)

Creates an uninitialised array of a specified shape.
  • How it works: Instead of setting elements to zero or one, np.empty simply allocates the memory block and leaves whatever garbage values were already present in those memory addresses.
  • Why use it: It is faster than np.zeros, np.ones, or np.random because it avoids the overhead of value initialisation. It is highly useful when you plan to immediately overwrite every element in the array anyway.

Range Creation (np.arange)

Generates sequences of numbers over a range.
  • Syntax: np.arange([start], stop, [step])
  • Default values: start defaults to 0, and step defaults to 1.
  • Inclusivity: The stop value is not inclusive.

Linearly Spaced Arrays (np.linspace)

Generates a specified number of evenly spaced values over a specified interval.
  • Key Difference from arange: Instead of specifying a step size, you specify the exact number of elements you want.
  • Defaults: Returns elements as float64 by default. You can explicitly override this using the dtype parameter.

Random Number Generation (np.random)

Modern NumPy uses a default generator object for producing random numbers.
  • To use it, instantiate the generator first using np.random.default_rng().
  • Generate random integers within a range using the .integers(low, high, size) method.

4. Array Properties

Knowing your array properties is essential for debugging structural errors and matching dimensional shapes. Mental Model for Multi-Dimensional Layouts: In mathematical notation, we access a 2D matrix by row index first and column index second. For higher dimensions, a helpful mental model is that the column index comes last, and the row index is second-to-last.

5. Indexing and Slicing

Accessing Elements

NumPy is zero-indexed. You access elements using square brackets.
  • Positive Indexing: a[0] accesses the first element.
  • Negative Indexing: a[-1] accesses the last element, and a[-2] accesses the second-to-last element.

Modifying Elements

You can mutate array elements in place by assigning a value directly to a targeted index.

Basic Slicing

Extracts sections of an array using [start:stop:step] notation.
  • Slices include the start index but exclude the stop index.

2D Array Slicing

For a two-dimensional array, the syntax is arr[row_slice, column_slice]. You separate row and column instructions with a comma:

Conditional (Boolean) Slicing

This is a powerful filtering technique where elements are selected based on logical conditions.
  1. Generating a Boolean Mask: Running an operator like a < 6 returns an array of True/False flags corresponding to whether each element meets the condition.
  2. Filtering: Passing this mask back into the array returns a flat, new array containing only the elements where the mask is True.
  • Multiple Conditions: You can combine multiple logical conditions. You must wrap each condition in round brackets and use bitwise operators (& for AND, | for OR).
  • Retrieving Indices of Matches (np.nonzero): If you want to find the indices where the condition is met instead of the values themselves, use np.nonzero(condition). It returns a tuple of arrays (one for each dimension) containing coordinates of matching elements.

6. Array Manipulation

Reshaping Arrays (reshape)

Changes the shape structure of an array without modifying its underlying data.
  • Sizing Constraint: The total size (number of elements) in the reshaped array must exactly match the original array size, otherwise NumPy throws a ValueError.
  • Row-Major vs. Column-Major Order (order): You can control how elements are read/placed in memory during a reshape:
    • order='C' (default, C-like order): Fills elements row-by-row.
    • order='F' (Fortran-like order): Fills elements column-by-column.

Flattening Arrays (flatten, ravel)

Converts a multi-dimensional array into a flat 1D array.
  • ravel (Shallow/View): Returns a flattened view of the original array. Changes made to the ravelled array will directly mutate the original parent array. It is highly memory-efficient because no copy of the underlying data is made.
  • flatten (Deep Copy): Returns a completely new 1D copy of the array. Modifying the flattened array will not affect the parent array.

Transposing Arrays (transpose, .T)

Transposes the matrix by swapping row indices with column indices.
  • Access via .T or the .transpose() method.
  • Transposing twice returns the original array structure. It does not modify the original parent array in place.

Reversing/Flipping Arrays (flip, slicing)

Reverses the order of elements along axes.
  • np.flip(arr): Flips the elements across all axes.
  • Axis-Specific Flipping:
    • np.flip(arr, axis=0) reverses vertically along the rows.
    • np.flip(arr, axis=1) reverses horizontally along the columns.
  • Sub-array Flipping: You can flip targeted portions. For example, np.flip(arr[1]) reverses only the second row, while np.flip(arr[:, 1]) reverses only the second column.

7. Combining and Splitting Arrays

Concatenation (concatenate)

Combines multiple arrays along a specified axis.
  • By default, axis=0 is used, which vertically stacks them.
  • Constraint: All arrays must have matching dimensions except along the concatenation axis, or NumPy will raise an error.

Vertical Stacking (vstack)

Stacks arrays on top of each other (vertically along axis=0).

Horizontal Stacking (hstack)

Stacks arrays adjacent to each other side-by-side (horizontally along axis=1).

Horizontal Splitting (hsplit)

Splits an array horizontally along columns.
  • Split into Equal Sections: Pass an integer specifying the number of equal sub-arrays.
  • Split at Specific Coordinates: Pass a list of column indices where cuts should happen.

8. Sorting and Copying

Sorting Arrays (sort, argsort, partition)

  • np.sort: Returns a sorted copy of the array.
  • np.argsort: Returns the indices that would sort the array, allowing you to perform indirect sorting.
  • np.partition: Partitions an array around a specified index k. All elements smaller than the element at k are shuffled to the left, and all larger elements are moved to the right. The elements on either side are not guaranteed to be sorted. This is highly useful for top-k selection algorithms.

Views vs. Copies

Understanding memory management in NumPy is critical to preventing unintended mutations.
  • Views (Shallow Copies): To save memory and execution overhead, basic operations like slicing and indexing return views rather than copies. If you modify a slice, the changes propagate to the original array.
  • Deep Copies: If you need a completely isolated array, call the .copy() method explicitly to allocate separate memory.

9. Aggregate Functions

Aggregations can collapse an entire array or be calculated along specific dimensions using the axis parameter.
  • axis=0 (Rows Axis): Computes operations vertically down columns.
  • axis=1 (Columns Axis): Computes operations horizontally across rows.

10. Mathematical Vector Operations

What are Vector Operations?

Vector operations in NumPy allow mathematical operations to be performed on entire arrays at once, instead of processing one element at a time using loops. This concept is known as vectorization. NumPy internally uses highly optimized C code, making these operations much faster and more efficient than traditional Python loops.
Key Idea: Perform operations on the whole array with a single statement.

Example 1: Vector Addition

Output
Explanation

Example 2: Vector Subtraction

Output

Example 3: Vector Multiplication

Output
Explanation Each element of the first array is multiplied by the corresponding element of the second array.

Example 4: Vector Division

Output

Example 5: Scalar Operations

A scalar is a single numeric value. NumPy automatically applies the scalar to every element of the array.
Output

Example 6: Power Operation

Output

Example 7: Square Root

Output

Example 8: Trigonometric Functions

Output
Similarly, NumPy provides many mathematical functions that operate element-wise:

Why Vectorization?

Without NumPy

Using NumPy

Advantages
  • No explicit loops
  • Less code
  • Faster execution
  • Better readability
  • Optimized memory usage

Real-World Example

Suppose an employee receives a ₹5,000 salary increment.
Output
The increment is automatically applied to every employee.

11. Broadcasting

Concept of Broadcasting

  • Definition: A feature in NumPy that allows arithmetic operations between arrays of different shapes without manually reshaping them or copying data.
  • How it works: NumPy automatically takes the smaller array (or scalar) and expands its dimensions to match the larger array.

How Shapes Expand to Match (Broadcasting Rules)

  1. Right-to-Left Alignment: NumPy aligns array shapes starting from the trailing (rightmost) dimensions.
  2. Dimension Expansion: Any dimension of size 1 (or a scalar) is stretched/duplicated along that axis to match the larger dimension.
  3. Compatibility: If two dimensions are unequal and neither is 1, NumPy cannot expand them and raises a ValueError.

Example 1: Scalar Expansion (0D →\rightarrow 1D)

Output
Intermediate Expansion Step:

Example 2: Scalar to Matrix Expansion (0D →\rightarrow 2D)

Output
Intermediate Expansion Step:

Example 3: Row Vector Expansion (1x3 →\rightarrow 2x3)

Output
Intermediate Expansion Step:

Example 4: Column Vector Expansion (2x1 →\rightarrow 2x3)

Output
Intermediate Expansion Step:

Example 5: Student Marks

Suppose every student receives 5 bonus marks in every subject.
Output
The bonus marks are added to every row automatically.

Example 6: Image Brightness

Output
Each pixel becomes brighter.

Example 7: Temperature Conversion

Convert Celsius temperatures into Fahrenheit. Formula:
Output

Example 8: Broadcasting Failure

Output
Reason
Comparing from the right:
Therefore, broadcasting is not possible.

Broadcasting Compatibility

Real-World Analogy

Imagine a teacher announces:
“Everyone gets 5 grace marks.”
Instead of giving each student the marks individually, the same bonus is automatically applied to every student. Broadcasting works in exactly the same way—NumPy automatically applies smaller arrays wherever they fit.

Key Points

  • Broadcasting enables operations on arrays of different shapes.
  • Smaller arrays are virtually expanded without copying data.
  • Shapes must satisfy broadcasting rules.
  • Broadcasting improves both memory efficiency and performance.
  • It is one of NumPy’s most powerful features for scientific computing and machine learning.

12. Broadcasting and Vectorization - How They Work Together

  • Key Concept: Students often think Broadcasting and Vectorization are identical. In reality, they are two distinct operations working in tandem.
  • Step 1: Broadcasting (Shape Compatibility):
    • Mechanism: Makes arrays of different shapes compatible by logically expanding the smaller array.
    • Efficiency: No actual data duplication or copying occurs in memory.
    • Purpose: Resolves shape mismatches to enable operations.
  • Step 2: Vectorization (Element-wise Computation):
    • Mechanism: Executes operations on all elements simultaneously.
    • Efficiency: Offloads iterations from interpreted Python loops to optimized precompiled C code.
    • Purpose: Performs high-speed computation once array shapes are aligned.

Example

Output

Step 1: Broadcasting

The shapes of the arrays are
Since the last dimensions are compatible, NumPy logically expands the second array.
Broadcasted (Logical Expansion)
Important: NumPy does not actually create this expanded array in memory. This is only a conceptual view to understand broadcasting.

Step 2: Vectorization

After broadcasting, NumPy performs the addition on all corresponding elements simultaneously.
Result

Visual Representation

Real-World Analogy

Imagine a classroom with two rows of students. The teacher announces:
“Each student receives 5 bonus marks.”
Instead of writing the value 5 for every student individually, the same value is automatically applied to every student in each row.
  • Broadcasting is like extending the single bonus value wherever it is needed.
  • Vectorization is like adding those bonus marks to every student’s score simultaneously.

Broadcasting vs Vectorization

Key Takeaways

  • Broadcasting and Vectorization are not the same.
  • Broadcasting prepares arrays by making their shapes compatible.
  • Vectorization performs the mathematical operation on the compatible arrays.
  • Broadcasting does not create extra copies of the data; the expansion is only logical.
  • Together, broadcasting and vectorization make NumPy fast, memory-efficient, and ideal for scientific computing, data analysis, and machine learning.
Easy to Remember:
Broadcasting prepares the arrays → Vectorization performs the computation.

Practice and Next Steps

Before moving to the next section, make sure to practice your NumPy skills using the interactive notebook:

NumPy Practice Exercise

Practice your skills using the interactive notebook.💻 VS Code | 🚀 Colab | 📥 Download

Next Library: Pandas

Learn data manipulation and analysis using Pandas DataFrames and Series.