Skip to main content

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°
Why Clamp 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.

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:

FunctionPurpose
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
tip

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°
Why atan2 Is More Robust

np.arctan2(sin_theta, cos_theta) is numerically more stable than either arccos or arcsin alone because:

  • arccos loses precision when vectors are nearly parallel (θ ≈ 0°).
  • arcsin loses precision when vectors are nearly perpendicular (θ ≈ 90°).
  • atan2 maintains 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

MethodDimensionsAccuracyDependenciesBest For
Pure Python (dot product)AnyGood (with clamping)NoneSimple scripts, no dependencies
NumPy (dot product)AnyGood (with np.clip)numpyGeneral-purpose, production code
Cross product + atan23D only✅ BestnumpyMaximum numerical stability
Batch NumPyAnyGoodnumpyProcessing 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 arccos for 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 atan2 for 3D vectors when you need maximum numerical stability.
  • Always clamp the cosine value to [-1, 1] before passing it to arccos to prevent floating-point domain errors.

For most applications, the NumPy dot product approach strikes the best balance of simplicity, performance, and reliability.