⬡ Hub
Skip to content

NumPy: Array Operations and Mathematical Functions

NumPy's power lies in its ability to perform operations on entire arrays (or subsets of them) without explicit Python loops. This vectorized computation is significantly faster and more memory-efficient, making it ideal for numerical tasks. This document explores various array operations and common mathematical functions.

1. Element-wise Operations

Arithmetic operations (addition, subtraction, multiplication, division) between NumPy arrays (of compatible shapes) or between an array and a scalar are performed element-wise.

import numpy as np

arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])

# Addition
print("arr1 + arr2:", arr1 + arr2)

# Subtraction
print("arr1 - arr2:", arr1 - arr2)

# Multiplication
print("arr1 * arr2:", arr1 * arr2) # Element-wise multiplication, NOT matrix multiplication

# Division
print("arr1 / arr2:", arr1 / arr2)

# Exponentiation
print("arr1 ** 2:", arr1 ** 2)

# Operations with scalars
print("arr1 + 10:", arr1 + 10)
print("arr2 * 2:", arr2 * 2)

2. Universal Functions (ufuncs)

NumPy provides a set of "universal functions" (ufuncs) that operate element-wise on ndarrays. These are highly optimized C functions, making them very fast.

import numpy as np

arr = np.array([-1.5, 0.0, 2.5, 3.0])

# Absolute value
print("np.abs(arr):", np.abs(arr))

# Square root
print("np.sqrt(arr):", np.sqrt(np.array([1, 4, 9, 16]))) # Operates on positive values

# Exponential
print("np.exp(arr):", np.exp(arr))

# Logarithm
print("np.log(arr):", np.log(np.array([1, np.exp(1), np.exp(2)])))

# Trigonometric functions
print("np.sin(arr):", np.sin(arr))
print("np.cos(arr):", np.cos(arr))

# Ceiling and Floor
print("np.ceil(arr):", np.ceil(arr))
print("np.floor(arr):", np.floor(arr))

# Rounding
print("np.round(arr):", np.round(arr))

3. Aggregation Functions

NumPy provides functions to summarize data along an axis. Common aggregations include sum(), mean(), std(), min(), max(), argmin(), argmax().

import numpy as np

arr_2d = np.array([[1, 2, 3],
                   [4, 5, 6]])
print("2D Array:\n", arr_2d)

# Sum of all elements
print("\nSum of all elements:", arr_2d.sum())

# Sum along columns (axis=0)
print("Sum along columns (axis=0):", arr_2d.sum(axis=0)) # Sum of [1,4], [2,5], [3,6]

# Sum along rows (axis=1)
print("Sum along rows (axis=1):", arr_2d.sum(axis=1)) # Sum of [1,2,3], [4,5,6]

# Mean
print("\nMean of all elements:", arr_2d.mean())
print("Mean along rows (axis=1):", arr_2d.mean(axis=1))

# Standard Deviation
print("\nStandard Deviation of all elements:", arr_2d.std())

# Min and Max
print("\nMinimum element:", arr_2d.min())
print("Maximum element along columns (axis=0):", arr_2d.max(axis=0))

# Argmin and Argmax (index of min/max)
print("Index of minimum element (flattened):", arr_2d.argmin())
print("Index of maximum element along rows (axis=1):", arr_2d.argmax(axis=1))

4. Matrix Operations (Linear Algebra)

NumPy is excellent for linear algebra.

import numpy as np

matrix1 = np.array([[1, 2], [3, 4]])
matrix2 = np.array([[5, 6], [7, 8]])
vector = np.array([9, 10])

print("Matrix 1:\n", matrix1)
print("\nMatrix 2:\n", matrix2)
print("\nVector:\n", vector)

# Matrix multiplication (dot product)
# Using np.dot()
dot_product = np.dot(matrix1, matrix2)
print("\nMatrix1 . Matrix2 (np.dot):\n", dot_product)

# Using @ operator (Python 3.5+)
at_operator = matrix1 @ matrix2
print("\nMatrix1 @ Matrix2 (@ operator):\n", at_operator)

# Matrix-vector multiplication
matrix_vec_mult = matrix1 @ vector
print("\nMatrix1 @ Vector:\n", matrix_vec_mult)

# Transpose
print("\nTranspose of Matrix 1:\n", matrix1.T)

# Inverse (requires square matrix)
try:
    inverse_matrix = np.linalg.inv(matrix1)
    print("\nInverse of Matrix 1:\n", inverse_matrix)
except np.linalg.LinAlgError as e:
    print(f"\nError computing inverse: {e}")

# Determinant
print("\nDeterminant of Matrix 1:", np.linalg.det(matrix1))

5. Broadcasting

Broadcasting is NumPy's way of dealing with arrays of different shapes during arithmetic operations. It effectively "stretches" arrays so they have compatible shapes for element-wise operations, without actually making copies of the data.

import numpy as np

arr = np.array([[0, 0, 0],
                [10, 10, 10],
                [20, 20, 20],
                [30, 30, 30]])
add_row = np.array([1, 2, 3])

print("Array:\n", arr)
print("\nAdd Row:\n", add_row)

# add_row is broadcast across each row of arr
result = arr + add_row
print("\nResult of Array + Add Row (Broadcasting):\n", result)

# Example with 2D array and 1D array column-wise
arr_col = np.array([[0], [1], [2], [3]])
add_col = np.array([100, 200, 300]) # This will cause an error unless shapes are explicitly compatible or manipulated
# result_col_error = arr_col + add_col # This would raise a ValueError
# To make it work, add_col needs to be reshaped to (1,3)
add_col_reshaped = add_col.reshape(1, 3)
# Or for adding to rows:
# arr_col + np.array([100]) # This would broadcast correctly

# A more common broadcasting example: Subtracting mean from each column
matrix = np.random.rand(3, 4)
mean_per_column = matrix.mean(axis=0) # Shape (4,)
result_sub_mean = matrix - mean_per_column # (3,4) - (4,) -> (3,4)
print("\nMatrix:\n", matrix)
print("\nMean per column:\n", mean_per_column)
print("\nMatrix minus mean per column (Broadcasting):\n", result_sub_mean)

Further Topics:

  • Array comparison and boolean operations
  • Sorting arrays (np.sort, arr.sort())
  • Set operations (union, intersection)
  • Advanced broadcasting rules and examples

Vectorized operations and broadcasting are key to writing concise, efficient, and readable numerical code with NumPy. Mastering these concepts will significantly enhance your productivity in data science and scientific computing.