Skip to main content

How to Do Efficient Array Multiplications using NumPy in Python

NumPy offers multiple ways to perform multiplication, each serving a distinct mathematical purpose. Confusing element-wise multiplication with matrix multiplication is a common source of bugs in scientific computing.

This guide explains the differences between numpy.multiply, numpy.dot, numpy.matmul, and the operators * and @.

Setup and Sample Data

Before exploring the operations, let's define two 2x2 matrices (A and B) to serve as our base examples.

import numpy as np

# Create sample 2x2 arrays
A = np.array([[1, 2],
[3, 4]])

B = np.array([[5, 6],
[7, 8]])

print(f"Matrix A:\n{A}")
print(f"Matrix B:\n{B}")

Output:

Matrix A:
[[1 2]
[3 4]]
Matrix B:
[[5 6]
[7 8]]

Element-wise Multiplication (* and np.multiply)

Element-wise multiplication multiplies the value at A[i][j] with the value at B[i][j]. The resulting matrix has the same shape as the inputs.

Using np.multiply

This function is explicit and performs strict element-by-element multiplication.

import numpy as np

# Create sample 2x2 arrays
A = np.array([[1, 2],
[3, 4]])

B = np.array([[5, 6],
[7, 8]])

# ✅ Correct: Element-wise multiplication
C = np.multiply(A, B)
print(f"np.multiply(A, B):\n{C}")

Output:

np.multiply(A, B):
[[ 5 12]
[21 32]]

Using the * Operator

The asterisk * is the shorthand for element-wise multiplication. It also supports scalar broadcasting, where a single number is multiplied against every element in the array.

import numpy as np

# Create sample 2x2 arrays
A = np.array([[1, 2],
[3, 4]])

B = np.array([[5, 6],
[7, 8]])

# ✅ Correct: Element-wise multiplication using operator
C_operator = A * B
print(f"A * B:\n{C_operator}")

# ✅ Correct: Scalar multiplication (Broadcasting)
D_scalar = A * 2
print(f"A * 2:\n{D_scalar}")

Output:

A * B:
[[ 5 12]
[21 32]]
A * 2:
[[2 4]
[6 8]]
note

If you are coming from MATLAB or linear algebra textbooks, remember that in Python/NumPy, A * B is not a matrix dot product. It is element-wise.

Matrix Multiplication (@, np.dot, np.matmul)

Matrix multiplication (the dot product of rows and columns) results in a matrix where C[i][j] is the dot product of the i-th row of A and the j-th column of B.

Using np.dot

This is the classic function for matrix multiplication.

import numpy as np

# Create sample 2x2 arrays
A = np.array([[1, 2],
[3, 4]])

B = np.array([[5, 6],
[7, 8]])

# ✅ Correct: Standard matrix multiplication
C_dot = np.dot(A, B)
print(f"np.dot(A, B):\n{C_dot}")

Output:

np.dot(A, B):
[[19 22]
[43 50]]

Calculations: Row 1 [1, 2] dot Col 1 [5, 7] = 1*5 + 2*7 = 19.

Using the @ Operator and np.matmul

np.matmul is the modern standard for matrix multiplication. The @ operator (introduced in Python 3.5) is syntactic sugar for np.matmul.

import numpy as np

# Create sample 2x2 arrays
A = np.array([[1, 2],
[3, 4]])

B = np.array([[5, 6],
[7, 8]])

# ✅ Correct: Matrix multiplication using @ (Recommended)
C_at = A @ B
print(f"A @ B:\n{C_at}")

# ✅ Correct: Using explicit function
C_matmul = np.matmul(A, B)
print(f"np.matmul(A, B):\n{C_matmul}")

Output:

A @ B:
[[19 22]
[43 50]]
np.matmul(A, B):
[[19 22]
[43 50]]

Advanced: The Difference Between dot and matmul

For 2D arrays, np.dot and np.matmul behave identically. However, for higher-dimensional arrays (3D+), they diverge significantly:

  • np.matmul (or @): Treats the arrays as a stack of matrices residing in the last two indices. It broadcasts accordingly (Batch Matrix Multiplication).
  • np.dot: Performs a sum product over the last axis of the first array and the second-to-last of the second array (Tensor contraction).
import numpy as np

# Create sample 2x2 arrays
A = np.array([[1, 2],
[3, 4]])

B = np.array([[5, 6],
[7, 8]])

# Define two 3-D arrays (Batch of 2, 2x2 matrices)
a_3d = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])

b_3d = np.array([[[9, 10], [11, 12]],
[[13, 14], [15, 16]]])

# Case 1: matmul (Batch Processing)
# Result shape: (2, 2, 2)
res_matmul = np.matmul(a_3d, b_3d)
print(f"Matmul Shape: {res_matmul.shape}")

# Case 2: dot (Tensor Contraction)
# Result shape: (2, 2, 2, 2)
res_dot = np.dot(a_3d, b_3d)
print(f"Dot Shape: {res_dot.shape}")

Output:

Matmul Shape: (2, 2, 2)
Dot Shape: (2, 2, 2, 2)
tip

For most modern Machine Learning and Data Science applications involving batches of data, np.matmul (or @) is usually what you want.

Conclusion

To perform multiplication in NumPy efficiently and correctly:

  1. Use * for Element-wise multiplication (e.g., scaling an image, masking).
  2. Use @ for Matrix multiplication (e.g., linear algebra, neural network layers).
  3. Use np.matmul if you need specific broadcasting rules for high-dimensional arrays (batches).
  4. Avoid np.dot for high-dimensional arrays unless you specifically intend to perform tensor contraction.