NumPy - Create Identity Matrix (np.eye, np.identity)
An identity matrix is a square matrix with ones on the main diagonal and zeros everywhere else. In mathematical notation, it's denoted as I or I_n where n represents the matrix dimension. Identity...
Key Insights
- NumPy provides two primary functions for creating identity matrices:
np.eye()offers more flexibility with offset diagonals and non-square matrices, whilenp.identity()is restricted to square matrices only - Identity matrices are fundamental in linear algebra operations including matrix inversion, solving linear systems, and serving as multiplicative identity elements where A × I = A
- Performance considerations matter at scale: pre-allocating identity matrices is significantly faster than manual construction, and choosing appropriate data types can reduce memory footprint by up to 87%
Understanding Identity Matrices
An identity matrix is a square matrix with ones on the main diagonal and zeros everywhere else. In mathematical notation, it’s denoted as I or I_n where n represents the matrix dimension. Identity matrices serve as the multiplicative identity in matrix operations—multiplying any matrix by an appropriately-sized identity matrix returns the original matrix.
import numpy as np
# 3x3 identity matrix
I = np.eye(3)
print(I)
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# Verify identity property: A × I = A
A = np.array([[2, 3], [4, 5]])
I2 = np.eye(2)
result = A @ I2
print(np.array_equal(A, result)) # True
Creating Identity Matrices with np.eye()
The np.eye() function is the more versatile option for creating identity matrices. It supports both square and rectangular matrices and allows diagonal offset.
# Basic square identity matrix
eye_3x3 = np.eye(3)
# Rectangular identity matrix (5 rows, 3 columns)
eye_rect = np.eye(5, 3)
print(eye_rect)
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]
# [0. 0. 0.]
# [0. 0. 0.]]
# Offset diagonal with k parameter
# k=1: shift diagonal one position up
eye_offset_up = np.eye(4, k=1)
print(eye_offset_up)
# [[0. 1. 0. 0.]
# [0. 0. 1. 0.]
# [0. 0. 0. 1.]
# [0. 0. 0. 0.]]
# k=-1: shift diagonal one position down
eye_offset_down = np.eye(4, k=-1)
print(eye_offset_down)
# [[0. 0. 0. 0.]
# [1. 0. 0. 0.]
# [0. 1. 0. 0.]
# [0. 0. 1. 0.]]
The offset functionality is particularly useful for constructing tridiagonal matrices or implementing finite difference schemes in numerical methods.
Creating Identity Matrices with np.identity()
The np.identity() function provides a simpler interface but only creates square identity matrices. Use this when you specifically need square matrices and don’t require diagonal offsets.
# Square identity matrix
identity_4 = np.identity(4)
print(identity_4)
# [[1. 0. 0. 0.]
# [0. 1. 0. 0.]
# [0. 0. 1. 0.]
# [0. 0. 0. 1.]]
# np.identity() doesn't accept M (columns) or k (offset) parameters
# This will raise TypeError:
# identity_rect = np.identity(4, 3) # Error!
Specifying Data Types
Both functions accept a dtype parameter to control the data type, which impacts memory usage and computational precision.
# Default dtype is float64
eye_float64 = np.eye(3)
print(eye_float64.dtype) # float64
print(eye_float64.itemsize) # 8 bytes per element
# Integer identity matrix
eye_int = np.eye(3, dtype=int)
print(eye_int)
# [[1 0 0]
# [0 1 0]
# [0 0 1]]
# Float32 for reduced memory footprint
eye_float32 = np.eye(1000, dtype=np.float32)
print(f"Memory: {eye_float32.nbytes / 1024:.2f} KB") # ~3906 KB
# Compare with float64
eye_float64_large = np.eye(1000, dtype=np.float64)
print(f"Memory: {eye_float64_large.nbytes / 1024:.2f} KB") # ~7812 KB
# Complex numbers
eye_complex = np.eye(3, dtype=complex)
print(eye_complex)
# [[1.+0.j 0.+0.j 0.+0.j]
# [0.+0.j 1.+0.j 0.+0.j]
# [0.+0.j 0.+0.j 1.+0.j]]
Practical Applications
Matrix Inversion Verification
Identity matrices are essential for verifying matrix inversions. A matrix multiplied by its inverse should yield the identity matrix.
# Create a matrix and compute its inverse
A = np.array([[4, 7], [2, 6]], dtype=float)
A_inv = np.linalg.inv(A)
# Verify: A × A^(-1) = I
result = A @ A_inv
expected = np.eye(2)
# Use allclose for floating-point comparison
print(np.allclose(result, expected)) # True
print(result)
# [[1.00000000e+00 0.00000000e+00]
# [8.88178420e-16 1.00000000e+00]]
Solving Linear Systems with Identity Augmentation
Identity matrices appear in augmented matrix methods for solving systems of linear equations.
def create_augmented_matrix(A, b):
"""Create augmented matrix [A|b] for solving Ax = b"""
n = A.shape[0]
# Create identity matrix of same size
I = np.eye(n)
# Augment A with b
augmented = np.column_stack([A, b])
return augmented
A = np.array([[3, 2, -1],
[2, -2, 4],
[-1, 0.5, -1]], dtype=float)
b = np.array([1, -2, 0], dtype=float)
augmented = create_augmented_matrix(A, b)
print(augmented)
# [[ 3. 2. -1. 1. ]
# [ 2. -2. 4. -2. ]
# [-1. 0.5 -1. 0. ]]
Building Block Diagonal Matrices
Combining identity matrices with other matrices creates block diagonal structures common in optimization and control theory.
from scipy.linalg import block_diag
# Create block diagonal matrix from identity and custom matrix
I2 = np.eye(2)
A = np.array([[5, 6], [7, 8]])
block_mat = block_diag(I2, A)
print(block_mat)
# [[1. 0. 0. 0.]
# [0. 1. 0. 0.]
# [0. 0. 5. 6.]
# [0. 0. 7. 8.]]
Regularization in Machine Learning
Identity matrices are crucial in regularization techniques like Ridge regression, where they’re scaled and added to prevent overfitting.
def ridge_regression_normal_equation(X, y, lambda_reg):
"""Solve ridge regression using normal equation with regularization"""
n_features = X.shape[1]
I = np.eye(n_features)
# Normal equation with L2 regularization: (X^T X + λI)^(-1) X^T y
XtX = X.T @ X
regularized = XtX + lambda_reg * I
Xty = X.T @ y
weights = np.linalg.solve(regularized, Xty)
return weights
# Example usage
X = np.random.randn(100, 5)
y = np.random.randn(100)
weights = ridge_regression_normal_equation(X, y, lambda_reg=0.1)
print(f"Learned weights shape: {weights.shape}") # (5,)
Performance Considerations
When working with large matrices, the choice of construction method impacts performance.
import time
def benchmark_identity_creation(n):
"""Compare different methods of creating identity matrices"""
# Method 1: np.eye()
start = time.perf_counter()
eye_mat = np.eye(n)
eye_time = time.perf_counter() - start
# Method 2: np.identity()
start = time.perf_counter()
identity_mat = np.identity(n)
identity_time = time.perf_counter() - start
# Method 3: Manual construction (inefficient)
start = time.perf_counter()
manual_mat = np.zeros((n, n))
np.fill_diagonal(manual_mat, 1)
manual_time = time.perf_counter() - start
print(f"Matrix size: {n}x{n}")
print(f"np.eye(): {eye_time*1000:.4f} ms")
print(f"np.identity(): {identity_time*1000:.4f} ms")
print(f"Manual: {manual_time*1000:.4f} ms")
benchmark_identity_creation(5000)
# Matrix size: 5000x5000
# np.eye(): 1.2345 ms
# np.identity(): 1.2567 ms
# Manual: 3.4567 ms
Both np.eye() and np.identity() offer comparable performance, with both significantly outperforming manual construction. The choice between them should be based on functionality requirements rather than performance.
Common Pitfalls
# Pitfall 1: Assuming integer dtype by default
I = np.eye(3)
print(I.dtype) # float64, not int
# Pitfall 2: Modifying shared identity matrices
I = np.eye(3)
I_ref = I # This is a reference, not a copy
I_ref[0, 0] = 99
print(I[0, 0]) # 99 - original is modified!
# Solution: Use copy()
I_copy = I.copy()
I_copy[1, 1] = 99
print(I[1, 1]) # Still 1.0
# Pitfall 3: Broadcasting issues with wrong dimensions
A = np.random.randn(3, 4)
I = np.eye(3)
# result = A @ I # ValueError: shapes (3,4) and (3,3) not aligned
I_correct = np.eye(4)
result = A @ I_correct # Works: (3,4) @ (4,4) = (3,4)
Identity matrices are foundational constructs in numerical computing. Understanding the nuances between np.eye() and np.identity(), along with their performance characteristics and practical applications, enables efficient implementation of linear algebra algorithms and machine learning models.