How to Perform Matrix Addition and Subtraction in Python
Matrix operations form the foundation of scientific computing, machine learning, and data analysis. Python offers multiple approaches to matrix arithmetic, from optimized NumPy operations to pure Python implementations. This guide covers both methods and helps you choose the right tool for your situation.
Using NumPy for Matrix Operations
NumPy provides the most efficient and readable approach to matrix mathematics. Its arrays support element-wise operations with standard arithmetic operators.
Basic Addition and Subtraction
import numpy as np
# Create two matrices
A = np.array([[1, 2, 3],
[4, 5, 6]])
B = np.array([[7, 8, 9],
[10, 11, 12]])
# Element-wise addition
sum_matrix = A + B
print("A + B:")
print(sum_matrix)
# Element-wise subtraction
diff_matrix = A - B
print("\nA - B:")
print(diff_matrix)
Output:
A + B:
[[ 8 10 12]
[14 16 18]]
A - B:
[[-6 -6 -6]
[-6 -6 -6]]
Using NumPy Functions
NumPy also provides explicit functions that offer additional control:
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# Using np.add() and np.subtract()
sum_result = np.add(A, B)
diff_result = np.subtract(A, B)
print(f"Sum:\n{sum_result}")
print(f"\nDifference:\n{diff_result}")
# In-place operations to save memory
C = np.array([[1, 2], [3, 4]])
np.add(C, B, out=C) # Result stored directly in C
print(f"\nIn-place addition:\n{C}")
Output:
Sum:
[[ 6 8]
[10 12]]
Difference:
[[-4 -4]
[-4 -4]]
In-place addition:
[[ 6 8]
[10 12]]
Use np.add(A, B, out=A) for in-place operations when working with large matrices. This avoids creating temporary arrays and reduces memory usage.
Broadcasting with Scalars
NumPy automatically broadcasts scalar values across matrices:
import numpy as np
A = np.array([[1, 2, 3],
[4, 5, 6]])
# Add scalar to all elements
print("A + 10:")
print(A + 10)
# Subtract scalar from all elements
print("\nA - 2:")
print(A - 2)
Output:
A + 10:
[[11 12 13]
[14 15 16]]
A - 2:
[[-1 0 1]
[ 2 3 4]]
Working with Different Shapes
Broadcasting also works with compatible array shapes:
import numpy as np
# 2x3 matrix
A = np.array([[1, 2, 3],
[4, 5, 6]])
# 1D array (treated as row)
row = np.array([10, 20, 30])
# Column vector
col = np.array([[100],
[200]])
print("Matrix + Row vector:")
print(A + row)
print("\nMatrix + Column vector:")
print(A + col)
Output:
Matrix + Row vector:
[[11 22 33]
[14 25 36]]
Matrix + Column vector:
[[101 102 103]
[204 205 206]]
Matrix dimensions must be compatible for operations. Adding a 2x3 matrix to a 2x2 matrix raises a ValueError. Always verify shapes with matrix.shape before operations.
Pure Python Implementation
When NumPy isn't available or you need to understand the underlying mechanics, implement matrix operations using nested lists.
Using List Comprehensions
def matrix_add(A, B):
"""Add two matrices represented as nested lists."""
return [
[a + b for a, b in zip(row_a, row_b)]
for row_a, row_b in zip(A, B)
]
def matrix_subtract(A, B):
"""Subtract matrix B from matrix A."""
return [
[a - b for a, b in zip(row_a, row_b)]
for row_a, row_b in zip(A, B)
]
# Example usage
A = [[1, 2, 3],
[4, 5, 6]]
B = [[7, 8, 9],
[10, 11, 12]]
print("A + B:")
for row in matrix_add(A, B):
print(row)
print("\nA - B:")
for row in matrix_subtract(A, B):
print(row)
Output:
A + B:
[8, 10, 12]
[14, 16, 18]
A - B:
[-6, -6, -6]
[-6, -6, -6]
Generalized Operation Function
Create a flexible function that handles any element-wise operation:
def matrix_operation(A, B, operation):
"""
Apply an operation element-wise to two matrices.
Args:
A: First matrix (nested list)
B: Second matrix (nested list)
operation: Function taking two numbers, returning a number
Returns:
Result matrix as nested list
"""
if len(A) != len(B) or len(A[0]) != len(B[0]):
raise ValueError("Matrices must have the same dimensions")
return [
[operation(a, b) for a, b in zip(row_a, row_b)]
for row_a, row_b in zip(A, B)
]
# Example usage
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
# Addition
result_add = matrix_operation(A, B, lambda a, b: a + b)
print(f"Addition: {result_add}")
# Subtraction
result_sub = matrix_operation(A, B, lambda a, b: a - b)
print(f"Subtraction: {result_sub}")
# Custom operation: average of elements
result_avg = matrix_operation(A, B, lambda a, b: (a + b) / 2)
print(f"Average: {result_avg}")
Output:
Addition: [[6, 8], [10, 12]]
Subtraction: [[-4, -4], [-4, -4]]
Average: [[3.0, 4.0], [5.0, 6.0]]
Traditional Loop Approach
For maximum clarity or debugging purposes:
def matrix_add_verbose(A, B):
"""Add matrices using explicit loops."""
rows = len(A)
cols = len(A[0])
# Initialize result matrix with zeros
result = [[0 for _ in range(cols)] for _ in range(rows)]
# Perform element-wise addition
for i in range(rows):
for j in range(cols):
result[i][j] = A[i][j] + B[i][j]
return result
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
result = matrix_add_verbose(A, B)
print(result) # [[6, 8], [10, 12]]
Performance Comparison
NumPy significantly outperforms pure Python for matrix operations:
import numpy as np
import time
def benchmark(size=500, iterations=100):
"""Compare NumPy vs pure Python performance."""
# Create test data
A_list = [[i + j for j in range(size)] for i in range(size)]
B_list = [[i * j for j in range(size)] for i in range(size)]
A_numpy = np.array(A_list)
B_numpy = np.array(B_list)
# Benchmark pure Python
start = time.perf_counter()
for _ in range(iterations):
result = [
[a + b for a, b in zip(row_a, row_b)]
for row_a, row_b in zip(A_list, B_list)
]
python_time = time.perf_counter() - start
# Benchmark NumPy
start = time.perf_counter()
for _ in range(iterations):
result = A_numpy + B_numpy
numpy_time = time.perf_counter() - start
print(f"Matrix size: {size}x{size}")
print(f"Pure Python: {python_time:.4f}s")
print(f"NumPy: {numpy_time:.4f}s")
print(f"Speedup: {python_time / numpy_time:.1f}x")
benchmark()
Typical Output:
Matrix size: 500x500
Pure Python: 4.2341s
NumPy: 0.0089s
Speedup: 475.7x
NumPy achieves this performance by executing operations in optimized C code and leveraging CPU vector instructions (SIMD). The difference becomes more pronounced with larger matrices.
Method Comparison
| Aspect | NumPy | Pure Python |
|---|---|---|
| Syntax | A + B | Nested comprehensions |
| Performance | Optimized C code | Interpreted loops |
| Memory | Efficient arrays | List overhead |
| Dependencies | Requires NumPy | No dependencies |
| Best for | Production code | Learning, minimal environments |
Practical Applications
import numpy as np
# Image brightness adjustment (simplified)
image = np.random.randint(0, 256, size=(100, 100))
brightened = np.clip(image + 50, 0, 255)
# Combining predictions from multiple models
model1_predictions = np.array([[0.8, 0.2], [0.6, 0.4]])
model2_predictions = np.array([[0.7, 0.3], [0.5, 0.5]])
ensemble = (model1_predictions + model2_predictions) / 2
# Calculating residuals in regression
actual = np.array([[10], [20], [30]])
predicted = np.array([[12], [18], [33]])
residuals = actual - predicted
For any serious numerical work, NumPy is the standard choice. Reserve pure Python implementations for educational purposes, dependency-free environments, or very small matrices where the overhead of importing NumPy isn't justified.