NumPy
NumPy is a fundamental Python library for scientific computing, providing a powerful n-dimensional array object (ndarray) and optimized routines for mathematical, logical, and statistical operations. Unlike Python lists, NumPy arrays have a fixed size and require uniform data types, ensuring efficient memory use and performance. Many scientific and mathematical Python packages rely on NumPy, converting inputs to its arrays for faster processing.
Array Creation
There are several ways to create arrays.
- Using numpy.array()
import numpy as np
np.array([1, 2, 3]) # 1D array
np.array([[1, 2, 3], [4, 5, 6]]) # 2D array
- Using Built-in Functions a) Creating Arrays with Zeros or Ones
np.zeros((2, 3)) # 2×3 array filled with zeros
np.ones((3, 3)) # 3×3 array filled with ones
b) Creating an Empty Array (Uninitialized)
np.empty((2, 3)) # Creates an uninitialized array (values are random) - Using arange() and linspace()
a) arange(start, stop, step) – Like Python’s range()
np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
b) linspace(start, stop, num) – Evenly spaced values
np.linspace(0, 10, 5) # [0. 2.5 5. 7.5 10.] - Creating Identity and Diagonal Matrices
a) Identity Matrix
np.eye(3) # 3×3 identity matrix
b) Diagonal Matrix
np.diag([1, 2, 3]) # Diagonal matrix with given elements - Creating Random Arrays
np.random.rand(2, 3) # 2×3 array of random numbers from [0, 1)
np.random.randint(1, 10, (2, 3)) # 2×3 array of random integers from 1 to 9
np.random.randn(3, 3) # 3×3 array from standard normal distribution - Creating Arrays with Specific Data Types
np.array([1, 2, 3], dtype=float) # Converts to float type
Some Important Attribute & Properties
NumPy arrays (ndarray) have several attributes that provide useful information about the array’s structure and properties. Here are the most important ones:
- ndim (Number of Dimensions)
arr = np.array([[1, 2, 3], [4, 5, 6]])
arr.ndim # Output: 2
- shape (Dimensions of the Array)
arr.shape # Output: (2, 3) - size (Total Number of Elements)
arr.size # Output: 6 - dtype (Data Type of Elements)
arr_float = np.array([1.0, 2.0, 3.0])
arr_float.dtype # Output: float64 - itemsize (Size of Each Element in Bytes)
arr.itemsize - nbytes (Total Memory Used)
arr.nbytes # Output: size * itemsize - T (Transpose of the Array)
arr.T
NumPy Universal Functions (ufuncs)
Universal functions (ufuncs) in NumPy perform element-wise operations on arrays. They are optimized for speed and work on entire arrays without requiring loops.
- Arithmetic Operations These functions perform element-wise arithmetic operations.
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
np.add(arr1, arr2) # [5 7 9]
np.subtract(arr1, arr2) # [-3 -3 -3]
np.multiply(arr1, arr2) # [4 10 18]
np.divide(arr1, arr2) # [0.25 0.4 0.5]
np.power(arr1, 2) # [1 4 9]
- Statistical & Aggregate Functions Perform calculations on entire arrays.
arr = np.array([1, 2, 3, 4, 5])
np.sum(arr) # 15
np.mean(arr) # 3.0
np.median(arr) # 3.0
np.std(arr) # Standard deviation
np.var(arr) # Variance
np.min(arr) # 1
np.max(arr) # 5
- Trigonometric Functions Used for trigonometric calculations.
angles = np.array([0, 30, 45, 60, 90])
radians = np.radians(angles) # Convert degrees to radians
np.sin(radians)
np.cos(radians)
np.tan(radians)
- Logarithmic & Exponential Functions Used for log and exponential calculations.
arr = np.array([1, 2, 3])
np.exp(arr) # Exponential e^x
np.log(arr) # Natural log
np.log10(arr) # Log base 10
np.log2(arr) # Log base 2
- Rounding Functions Used to round, floor, or ceil values.
arr = np.array([1.2, 2.5, 3.7])
np.round(arr) # [1. 2. 4.]
np.floor(arr) # [1. 2. 3.]
np.ceil(arr) # [2. 3. 4.]
- Comparison & Logical Functions Used to compare elements in arrays.
arr1 = np.array([1, 2, 3])
arr2 = np.array([3, 2, 1])
np.equal(arr1, arr2) # [False True False]
np.greater(arr1, arr2) # [False False True]
np.less(arr1, arr2) # [ True False False]
- Special Functions Some additional useful functions.
arr = np.array([-1, 0, 1, -2, 2])
np.abs(arr) # Absolute value
np.sign(arr) # Returns -1, 0, or 1 based on sign
np.clip(arr, -1, 1) # Clips values between -1 and 1
Indexing, Slicing, and Iterating
NumPy provides powerful ways to access and manipulate array elements using indexing, slicing, and iterating.
- Indexing (Accessing Elements) Similar to Python lists, NumPy allows indexing to access individual elements.
a) 1D Array Indexing
arr = np.array([10, 20, 30, 40, 50])
arr[0] # Output: 10
arr[-1] # Output: 50 (Negative indexing works)
b) 2D Array Indexing
arr2D = np.array([[1, 2, 3], [4, 5, 6]])
arr2D[0, 1] # Output: 2 (Row 0, Column 1)
arr2D[1, 2] # Output: 6 (Row 1, Column 2)
c) 3D Array Indexing
arr3D = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
arr3D[0, 1, 1] # Output: 4
- Slicing (Extracting Subarrays) Slicing allows selecting portions of an array using the syntax:
array[start:stop:step] a) 1D Array Slicing
arr = np.array([10, 20, 30, 40, 50])
arr[1:4] # [20 30 40]
arr[:3] # [10 20 30] (Start from index 0)
arr[2:] # [30 40 50] (Slice till the end)
arr[::2] # [10 30 50] (Step of 2)
arr[::-1] # [50 40 30 20 10] (Reverse array)
b) 2D Array Slicing
arr2D = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2D[:2, 1:]
[[2 3]
[5 6]] (First 2 rows, last 2 columns)
arr2D[:, 0]
[1 4 7] (All rows, first column)
arr2D[1:, ::2]
[[4 6]
[7 9]] (From second row, every second column)
- Iterating Over Arrays You can loop through NumPy arrays efficiently.
a) Iterating a 1D Array
arr = np.array([10, 20, 30])
for elem in arr:
print(elem)
b) Iterating a 2D Array (Row-wise)
arr2D = np.array([[1, 2, 3], [4, 5, 6]])
for row in arr2D:
print(row)
c) Iterating Each Element (Using nditer)
for elem in np.nditer(arr2D):
print(elem, end=” “)
d) Iterating with Index (Using ndenumerate)
for index, elem in np.ndenumerate(arr2D):
print(index, elem)
Shape Manipulation
Shape manipulation in NumPy allows you to change the structure (shape) of an array without altering its data. The key functions for shape manipulation are:
- reshape() – Change Shape Without Changing Data Converts an array into a new shape while maintaining the same number of elements.
arr = np.array([1, 2, 3, 4, 5, 6])
reshaped_arr = arr.reshape(2, 3) # Convert 1D → 2D
reshaped_arr
- ravel() – Flatten an Array Returns a 1D flattened array without modifying the original array.
arr2D = np.array([[1, 2, 3], [4, 5, 6]])
flattened = arr2D.ravel()
flattened - transpose() – Swap Rows and Columns Swaps the dimensions of an array (equivalent to .T).
arr = np.array([[1, 2], [3, 4], [5, 6]])
arr.T
- expand_dims() – Add a New Axis Adds a new dimension to an array.
arr = np.array([1, 2, 3])
new_arr = np.expand_dims(arr, axis=0) # Convert 1D → 2D (Row vector)
new_arr.shape # Output: (1, 3)
- squeeze() – Remove Single-Dimensional Entries Removes unnecessary dimensions from an array.
arr = np.array([[[1, 2, 3]]]) # Shape (1, 1, 3)
squeezed_arr = np.squeeze(arr) # Removes extra dimensions
squeezed_arr.shape # Output: (3,)
- concatenate() – Join Two Arrays Combines multiple arrays along an axis.
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6]])
result = np.concatenate((arr1, arr2), axis=0)
result
Stacking Arrays
Stacking in NumPy means combining multiple arrays along a specified axis. NumPy provides several functions to achieve this efficiently.
- hstack() – Horizontal Stacking Joins arrays side by side (along axis 1 for 2D arrays).
Example 1
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result = np.hstack((arr1, arr2))
result
Example 2
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6], [7, 8]])
result = np.hstack((arr1, arr2))
result
- vstack() – Vertical Stacking Stacks arrays on top of each other (along axis 0).
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result = np.vstack((arr1, arr2))
result
- dstack() – Depth Stacking Stacks arrays along a new third axis (depth-wise).
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result = np.dstack((arr1, arr2))
result
- column_stack() – Stack 1D Arrays as Columns Similar to hstack(), but ensures 1D arrays are converted to columns first.
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result = np.column_stack((arr1, arr2))
result
- row_stack() – Stack 1D Arrays as Rows Equivalent to vstack(), but ensures 1D arrays are converted into rows.
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result = np.row_stack((arr1, arr2))
result
Splitting Arrays
Splitting in NumPy allows you to divide a large array into multiple smaller sub-arrays along a specified axis.
- split() – Split an Array into Equal Parts The np.split() function splits an array into equal-sized sub-arrays.
arr = np.array([1, 2, 3, 4, 5, 6])
result = np.split(arr, 3) # Split into 3 equal parts
result
- array_split() – Split into Unequal Parts If the array cannot be divided equally, use array_split().
arr = np.array([1, 2, 3, 4, 5, 6, 7])
result = np.array_split(arr, 3)
result
- hsplit() – Split Horizontally (Columns) For 2D arrays, hsplit() splits along columns (axis=1).
arr2D = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
result = np.hsplit(arr2D, 2) # Split into 2 parts
result
- vsplit() – Split Vertically (Rows) For 2D arrays, vsplit() splits along rows (axis=0).
arr2D = np.array([[1, 2, 3], [4, 5, 6]])
result = np.vsplit(arr2D, 2)
result
- dsplit() – Split Along Depth (3D Arrays) For 3D arrays, dsplit() splits along depth (axis=2).
arr3D = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
result = np.dsplit(arr3D, 3) # Split into 3 parts along depth
result
Copies and Views
When working with NumPy arrays, modifying one array might affect another if they share memory. NumPy provides two ways to access data:
- View (Shallow Copy) → Shares data with the original array
- Copy (Deep Copy) → Creates an independent copy
- View (Shallow Copy) A view is a new array object that refers to the same data as the original array. Changes in the view affect the original array and vice versa.
arr = np.array([1, 2, 3, 4])
view_arr = arr.view() # Create a view
view_arr[0] = 100 # Modify the view
arr # Output: [100 2 3 4] (Original is affected)
view_arr # Output: [100 2 3 4]
- Copy (Deep Copy) A copy creates a completely new array with separate memory. Changes in the copy do not affect the original array.
arr = np.array([1, 2, 3, 4])
copy_arr = arr.copy() # Create a deep copy
copy_arr[0] = 100 # Modify the copy
Output: [1 2 3 4] (Original remains unchanged)
copy_arr
Broadcasting Rules
Broadcasting allows NumPy to perform operations on arrays of different shapes without explicitly reshaping or copying data. It makes operations more efficient by avoiding unnecessary memory usage.
When operating on two arrays, NumPy compares their shapes from right to left. Two dimensions are compatible if:
- They are equal, OR
- One of them is 1
If these conditions are met, NumPy stretches the smaller array to match the larger one.
- Same Shape → Element-wise Operation If arrays have the same shape, operations are performed element-wise.
arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.array([[10, 20, 30], [40, 50, 60]])
result = arr1 + arr2 # Element-wise addition
result
- Different Shapes But Compatible → Broadcasting Applies If the arrays have different shapes, NumPy automatically expands the smaller array.
Example 1: Broadcasting a 1D array across a 2D array
arr1 = np.array([[1, 2, 3], [4, 5, 6]]) # Shape (2,3)
arr2 = np.array([10, 20, 30]) # Shape (3,)
result = arr1 + arr2
result
Example 2: Broadcasting a Scalar
A scalar (single value) is broadcasted to match any shape.
arr = np.array([[1, 2, 3], [4, 5, 6]]) # Shape (2,3)
scalar = 10
result = arr + scalar # Scalar is broadcasted
result
- Incompatible Shapes → Error If broadcasting rules are not met, NumPy raises a ValueError.
arr1 = np.array([[1, 2, 3], [4, 5, 6]]) # Shape (2,3)
arr2 = np.array([[10], [20], [30]]) # Shape (3,1)
result = arr1 + arr2 # ❌ Error: Shapes are incompatible
Advanced Indexing
Advanced indexing in NumPy allows more powerful ways to access elements in an array compared to basic slicing. It includes integer indexing, boolean indexing, and fancy indexing.
- Integer Indexing (Fancy Indexing) Instead of slicing, you can select specific elements using a list or array of indices.
Example 1: Selecting specific elements
arr = np.array([10, 20, 30, 40, 50])
indices = [0, 2, 4] # Selecting elements at positions 0, 2, and 4
result = arr[indices]
result
Example 2: Fancy Indexing with 2D Arrays
arr2D = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
rows = [0, 1, 2] # Selecting specific rows
cols = [2, 1, 0] # Selecting specific columns
result = arr2D[rows, cols] # Picks (0,2), (1,1), (2,0)
result
- Boolean Indexing Boolean indexing selects elements based on conditions.
Example 1
arr = np.array([10, 20, 30, 40, 50])
result = arr[arr > 25] # Select elements greater than 25
result
Example 2: 2D array
arr2D = np.array([[10, 20, 30], [40, 50, 60]])
mask = arr2D > 30 # Boolean mask
arr2D[mask]
- np.ix_() – Cross Selection of Rows and Columns np.ix_() helps in selecting all combinations of row and column indices.
arr = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
rows = np.array([0, 2]) # Select rows 0 and 2
cols = np.array([1, 2]) # Select columns 1 and 2
result = arr[np.ix_(rows, cols)]
result
- Using np.r_ and np.c_ (Concatenation Tricks) NumPy provides np.r_ and np.c_ as shorthand tricks to concatenate arrays easily.
Example 1: np.r_ for Row Concatenation
result = np.r_[np.array([1, 2, 3]), np.array([4, 5, 6])]
result
Example 2: np.c_ for Column Stacking
result = np.c_[[1, 2, 3], [4, 5, 6]]
result - np.newaxis for Adding Dimensions np.newaxis allows adding a new axis without reshaping manually.
arr = np.array([1, 2, 3])
arr.shape # (3,)
Convert to column vector
column_vector = arr[:, np.newaxis]
column_vector.shape # (3,1)
Convert to row vector
row_vector = arr[np.newaxis, :]
row_vector.shape # (1,3)