How to Compute the Angle Between Vectors in Python
Computing the angle between two vectors is a fundamental operation in linear algebra with applications across machine learning, computer graphics, physics simulations, and data science. Whether you're calculating similarity between feature vectors, determining the direction of movement, or analyzing forces, knowing how to find the angle between vectors is essential.
In this guide, you'll learn multiple methods to compute the angle between vectors in Python, using pure math, NumPy, and the cross product, with clear explanations of the underlying formulas and practical examples.
The Math Behind Vector Angles
The angle between two vectors u and v is derived from the dot product formula:
u · v = ‖u‖ ‖v‖ cos(θ)
Solving for θ:
θ = arccos( (u · v) / (‖u‖ ‖v ‖) )
Where:
- u · v is the dot product of the vectors
- ‖u‖ and ‖v‖ are the magnitudes (lengths) of the vectors
- θ is the angle between them
Using Pure Python (No Libraries)
You can compute the angle using only Python's built-in math module, no external libraries required:
import math
def angle_between_vectors(u, v):
"""
Compute the angle between two vectors using the dot product.
Args:
u, v: Lists or tuples representing vectors.
Returns:
Tuple of (angle_radians, angle_degrees).
"""
# Dot product
dot_product = sum(a * b for a, b in zip(u, v))
# Magnitudes
magnitude_u = math.sqrt(sum(a ** 2 for a in u))
magnitude_v = math.sqrt(sum(b ** 2 for b in v))
# Cosine of the angle
cos_theta = dot_product / (magnitude_u * magnitude_v)
# Clamp to [-1, 1] to handle floating-point errors
cos_theta = max(-1.0, min(1.0, cos_theta))
# Angle in radians and degrees
angle_rad = math.acos(cos_theta)
angle_deg = math.degrees(angle_rad)
return angle_rad, angle_deg
u = [3, 4]
v = [5, -2]
angle_rad, angle_deg = angle_between_vectors(u, v)
print(f"Angle in radians: {angle_rad:.4f}")
print(f"Angle in degrees: {angle_deg:.2f}°")
Output:
Angle in radians: 1.3078
Angle in degrees: 74.93°
cos_theta to [-1, 1]?Floating-point arithmetic can produce values like 1.0000000000000002 or -1.0000000000000004, which are slightly outside the valid domain of arccos. Passing such values to math.acos() raises a ValueError:
import math
# This will crash
math.acos(1.0000000000000002)
Output:
ValueError: math domain error
Clamping the value with max(-1.0, min(1.0, cos_theta)) prevents this error while maintaining accuracy.
Using NumPy (Recommended)
NumPy provides optimized functions for vector operations, making the computation cleaner and significantly faster for large arrays:
import numpy as np
def angle_between_vectors(u, v):
"""Compute the angle between two vectors using NumPy."""
u = np.array(u, dtype=float)
v = np.array(v, dtype=float)
cos_theta = np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v))
angle_rad = np.arccos(np.clip(cos_theta, -1.0, 1.0))
return angle_rad, np.degrees(angle_rad)
u = [3, 4]
v = [5, -2]
angle_rad, angle_deg = angle_between_vectors(u, v)
print(f"Angle in radians: {angle_rad:.4f}")
print(f"Angle in degrees: {angle_deg:.2f}°")
Output:
Angle in radians: 1.3078
Angle in degrees: 74.93°
Key NumPy functions used:
| Function | Purpose |
|---|---|
np.dot(u, v) | Computes the dot product |
np.linalg.norm(u) | Computes the vector magnitude |
np.clip(val, -1, 1) | Clamps value to valid arccos range |
np.arccos(val) | Computes the inverse cosine |
np.degrees(rad) | Converts radians to degrees |
np.clip() is NumPy's equivalent of the manual clamping done in the pure Python version. Always use it when computing angles to avoid domain errors.
Using the Cross Product (3D Vectors)
For 3-dimensional vectors, the cross product provides an alternative method. The magnitude of the cross product relates to the sine of the angle:
‖u × v‖ = ‖u‖ ‖v‖ sin(θ)
However, the most robust approach combines both the dot product and cross product using atan2, which correctly handles all quadrants and avoids the precision issues of arccos and arcsin alone:
import numpy as np
def angle_between_3d_vectors(u, v):
"""
Compute the angle between two 3D vectors using atan2
with both cross product and dot product for maximum accuracy.
"""
u = np.array(u, dtype=float)
v = np.array(v, dtype=float)
cross = np.cross(u, v)
sin_theta = np.linalg.norm(cross)
cos_theta = np.dot(u, v)
angle_rad = np.arctan2(sin_theta, cos_theta)
return angle_rad, np.degrees(angle_rad)
u = [3, 2, 1]
v = [1, 0, 2]
angle_rad, angle_deg = angle_between_3d_vectors(u, v)
print(f"Angle in radians: {angle_rad:.4f}")
print(f"Angle in degrees: {angle_deg:.2f}°")
Output:
Angle in radians: 0.9303
Angle in degrees: 53.30°
atan2 Is More Robustnp.arctan2(sin_theta, cos_theta) is numerically more stable than either arccos or arcsin alone because:
arccosloses precision when vectors are nearly parallel (θ ≈ 0°).arcsinloses precision when vectors are nearly perpendicular (θ ≈ 90°).atan2maintains precision across the full range of angles.
Special Cases to Handle
Zero Vectors
A zero vector has no direction, so the angle is undefined:
import numpy as np
def safe_angle(u, v):
"""Compute angle with zero-vector protection."""
u = np.array(u, dtype=float)
v = np.array(v, dtype=float)
norm_u = np.linalg.norm(u)
norm_v = np.linalg.norm(v)
if norm_u == 0 or norm_v == 0:
raise ValueError("Cannot compute angle with a zero vector.")
cos_theta = np.dot(u, v) / (norm_u * norm_v)
angle_rad = np.arccos(np.clip(cos_theta, -1.0, 1.0))
return angle_rad, np.degrees(angle_rad)
try:
safe_angle([0, 0], [1, 2])
except ValueError as e:
print(f"Error: {e}")
Output:
Error: Cannot compute angle with a zero vector.
Common Angle Results
import numpy as np
def angle_deg(u, v):
u, v = np.array(u, dtype=float), np.array(v, dtype=float)
cos_t = np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v))
return np.degrees(np.arccos(np.clip(cos_t, -1, 1)))
# Parallel vectors (same direction)
print(f"Parallel: {angle_deg([1, 0], [3, 0]):.1f}°")
# Opposite vectors
print(f"Opposite: {angle_deg([1, 0], [-1, 0]):.1f}°")
# Perpendicular vectors
print(f"Perpendicular: {angle_deg([1, 0], [0, 1]):.1f}°")
# 45-degree angle
print(f"45 degrees: {angle_deg([1, 0], [1, 1]):.1f}°")
Output:
Parallel: 0.0°
Opposite: 180.0°
Perpendicular: 90.0°
45 degrees: 45.0°
Computing Angles for Multiple Vector Pairs
When processing many vector pairs (common in machine learning), NumPy's vectorized operations are far more efficient than looping:
import numpy as np
def batch_angles(vectors_u, vectors_v):
"""Compute angles between corresponding pairs of vectors."""
u = np.array(vectors_u, dtype=float)
v = np.array(vectors_v, dtype=float)
# Dot products for all pairs
dots = np.sum(u * v, axis=1)
# Magnitudes for all pairs
norms_u = np.linalg.norm(u, axis=1)
norms_v = np.linalg.norm(v, axis=1)
cos_theta = dots / (norms_u * norms_v)
angles_rad = np.arccos(np.clip(cos_theta, -1.0, 1.0))
return np.degrees(angles_rad)
u_vectors = [[1, 0], [0, 1], [1, 1], [3, 4]]
v_vectors = [[0, 1], [0, 1], [-1, 1], [5, -2]]
angles = batch_angles(u_vectors, v_vectors)
for u, v, angle in zip(u_vectors, v_vectors, angles):
print(f"{u} ↔ {v}: {angle:.2f}°")
Output:
[1, 0] ↔ [0, 1]: 90.00°
[0, 1] ↔ [0, 1]: 0.00°
[1, 1] ↔ [-1, 1]: 90.00°
[3, 4] ↔ [5, -2]: 74.93°
Quick Comparison of Methods
| Method | Dimensions | Accuracy | Dependencies | Best For |
|---|---|---|---|---|
| Pure Python (dot product) | Any | Good (with clamping) | None | Simple scripts, no dependencies |
| NumPy (dot product) | Any | Good (with np.clip) | numpy | General-purpose, production code |
Cross product + atan2 | 3D only | ✅ Best | numpy | Maximum numerical stability |
| Batch NumPy | Any | Good | numpy | Processing many vector pairs |
Conclusion
Computing the angle between vectors in Python is straightforward once you understand the underlying math:
- Use the dot product formula with
arccosfor a clean, general-purpose solution. This works for vectors of any dimension. - Use NumPy for cleaner syntax and significantly better performance, especially with large or numerous vectors.
- Use the cross product with
atan2for 3D vectors when you need maximum numerical stability. - Always clamp the cosine value to
[-1, 1]before passing it toarccosto prevent floating-point domain errors.
For most applications, the NumPy dot product approach strikes the best balance of simplicity, performance, and reliability.