NumPy Math

NumPy Math and Broadcasting

1NumPy Math: overview

NumPy maths applies functions and summaries to whole arrays, and broadcasting lets arrays of different shapes work together by stretching the smaller one, so you can standardise, rescale and combine data without writing loops.

GoalBy the end you can use ufuncs and aggregations with axis, predict how broadcasting will combine two shapes, reshape arrays, multiply matrices with @, standardise data into z-scores, and handle nan values and dtype overflow.

Before this lesson: NumPy Basics

2NumPy Math: diagram

scores.sum(axis=...) # shape (3, 4)scores.sum(axis=...) # shape (3, 4)t0t1t2t3sum(axis=1)s070809060300s160657055250s2889510077360sum(axis=0)218240260192axis=0 collapses the rows: one total per column (bottom row). axis=1 collapses the columns: one total per row (highlighted column).
The axis you name is the one that disappears. Summing along axis=0 walks down each column and leaves 4 results; summing along axis=1 walks across each row and leaves 3.

3NumPy Math: explained step by step

In NumPy Basics you did arithmetic on whole arrays. This lesson fills in the rest of the number-crunching toolbox: maths functions, summaries along an axis, broadcasting (how arrays of different shapes combine), reshaping, matrix products, and the awkward cases, missing values and number types.

Universal functions (ufuncs)

Functions such as np.sqrt, np.exp, np.log, np.abs and np.round work on every element at once and return a new array of the same shape. NumPy calls them ufuncs. Do not reach for math.sqrt on an array: the math module only understands single numbers.

Python
import numpy as np

areas = np.array([4.0, 9.0, 16.0, 20.25])
print(np.sqrt(areas))
print(np.abs(np.array([-3, 2, -7])))
print(np.round(np.exp(np.array([0.0, 1.0, 2.0])), 2))

Summaries and the axis argument

sum, mean, median, std, var, min and max squash an array into one number, and cumsum / cumprod keep the shape and give running totals or running products. On a 2-D array, axis chooses the direction. The axis you name is the one that disappears: axis=0 collapses the rows and leaves one result per column, axis=1 collapses the columns and leaves one result per row.

Python
import numpy as np

scores = np.array([[70, 80, 90, 60],
                   [60, 65, 70, 55],
                   [88, 95, 100, 77]])
print(scores.sum(axis=0))
print(scores.sum(axis=1))
print(np.round(scores.std(axis=1), 1))
print(np.cumsum(scores[0]))

Broadcasting: arrays of different shapes

When two shapes differ, NumPy stretches the smaller array to fit, without actually copying it. It compares the shapes from the right-hand end, and two dimensions are compatible when they are equal or when one of them is 1. A missing dimension counts as 1.

  • A scalar fits anything: table * 2.
  • A row of shape (3,) fits a table of shape (2, 3): the row is applied to every row of the table.
  • A column of shape (2, 1) fits the same table: the column is applied across every column.
Python
import numpy as np

table = np.array([[1, 2, 3],
                  [4, 5, 6]])
row = np.array([10, 20, 30])
col = np.array([[100], [200]])
print(table + row)
print(table + col)
print(table - table.mean(axis=0))

If no stretching can make the shapes match, you get ValueError: operands could not be broadcast together with shapes (2,3) (2,). The usual cure is to turn the 1-D array into a column with reshape(2, 1) or [:, None], or to ask a summary to keep its dimension with keepdims=True.

Python
import numpy as np

table = np.ones((2, 3))
try:
    table + np.array([1, 2])
except ValueError as err:
    print("Error:", err)
print(table + np.array([1, 2]).reshape(2, 1))

Changing shape and making ranges

reshape(rows, cols) re-arranges the same values (use -1 for one size and NumPy works it out), .T transposes rows and columns, and ravel() flattens to 1-D. np.arange(start, stop, step) stops before stop; np.linspace(start, stop, count) includes both ends and is safer for decimal steps.

Python
import numpy as np

months = np.arange(1, 13).reshape(4, 3)
print(months)
print(months.T.shape, months.ravel()[:5])
print(np.linspace(0, 1, 5))

Matrix products

a * b multiplies element by element. a @ b (the same as np.dot(a, b) for 2-D arrays) is the matrix product: every row of a is multiplied by the column of b and added up. The inner sizes must match: (n, k) @ (k, m) gives (n, m). A typical use is quantities @ prices, which gives one total per order.

Z-scores, clipping and percentiles

To standardise data, subtract the mean and divide by the standard deviation: (x - x.mean()) / x.std(). The result says how many standard deviations each value is from average, which puts columns with different units on the same scale. np.clip(x, low, high) limits values to a range, and np.percentile(x, 90) gives the value that 90% of the data falls below.

Missing values and number types

np.nan marks a missing float. It spreads: any sum or mean that includes a nan is nan. np.isnan() finds them, and np.nanmean, np.nansum and friends skip them. An array also has one fixed dtype, and the dtype decides the maths: / always gives floats, // keeps integers, and small integer types such as int8 silently wrap around when a result does not fit (100 + 100 becomes -56). Convert with astype() before the maths, not after.

Python
import numpy as np

readings = np.array([21.5, np.nan, 22.5, 23.0])
print(readings.mean(), np.nanmean(readings))
print(np.isnan(readings))
small = np.array([100, 120], dtype=np.int8)
print(small + small)
print(small.astype(np.int32) + small)
print(np.array([7, 8]) / 2, np.array([7, 8]) // 2)

Quick reference: NumPy Math

  • Python NumPy Cheat Sheet — NumPy stores numbers in fixed-type arrays and applies operations to every element at once: build with `np.arr…

4NumPy Math: syntax

import numpy as np

np.sqrt(a), np.exp(a), np.log(a), np.abs(a)   # element by element
np.round(a, 2)                                # round every value

a.sum(), a.mean(), a.std(), a.min(), a.max()  # whole array -> 1 number
np.median(a), a.var(), np.percentile(a, 90)
a.sum(axis=0)                  # down the columns: one result per column
a.sum(axis=1)                  # across the rows: one result per row
a.sum(axis=1, keepdims=True)   # keep it as a column (n, 1)
np.cumsum(a), np.cumprod(a)    # running totals / products

a + b                          # broadcasting: shapes compared from the right
a[:, None]                     # turn (n,) into a column (n, 1)

a.reshape(2, 3), a.reshape(-1, 3), a.T, a.ravel()
np.arange(0, 10, 2), np.linspace(0, 1, 5)

a @ b                          # matrix product (same as np.dot)
np.clip(a, low, high)
(a - a.mean()) / a.std()       # z-scores

np.isnan(a), np.nanmean(a)     # missing values
a.astype(np.float64)           # change dtype

5NumPy Math: run the example

Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.

Interactive example
import numpy as np

# rows = students, columns = three tests
scores = np.array([[72, 85, 60],
                   [90, 78, 55],
                   [66, 92, 70],
                   [81, 64, 95]])

means = scores.mean(axis=0)
stds = scores.std(axis=0)
z = (scores - means) / stds
print("Test averages:", np.round(means, 1))
print("Z-scores:\n", np.round(z, 2))
best = z.mean(axis=1).argmax()
print("Strongest overall: student", best)
print("90th percentile:", round(np.percentile(scores, 90), 1))
Expected outputTest averages: [77.2 79.8 70. ]
Z-scores:
 [[-0.58  0.51 -0.65]
 [ 1.4  -0.17 -0.97]
 [-1.24  1.18  0.  ]
 [ 0.41 -1.52  1.62]]
Strongest overall: student 3
90th percentile: 91.8

7NumPy Math: how the example works

Hover or tap a step to highlight its lines in the example above.

  1. Line 3-7A 4 x 3 array of marks: each row is a student and each column is a test.
  2. Line 9mean(axis=0) goes down the columns and returns 3 numbers, the average of each test.
  3. Line 10std(axis=0) returns the spread of each test, again 3 numbers. NumPy divides by the number of values (not by n - 1).
  4. Line 11Broadcasting at work: scores has shape (4, 3) while means and stds have shape (3,). Compared from the right, the 3s match, so each row of the table is shifted and scaled by the same three numbers. The result z is a new (4, 3) array of z-scores.
  5. Line 12np.round(means, 1) rounds all three averages at once to keep the printed output short.
  6. Line 13A z-score says how many standard deviations a mark is above (positive) or below (negative) its test's average, so marks from different tests can be compared fairly.
  7. Line 14-15z.mean(axis=1) averages each student's z-scores across the three tests, and argmax() returns the position of the highest one: student 3.
  8. Line 16np.percentile(scores, 90) looks at all 12 marks together: about 90% of them are at or below the answer. round() trims the printed decimals.

8NumPy Math: common mistakes

Using math.sqrt on an array

✗ Wrong

import math
import numpy as np

areas = np.array([4.0, 9.0, 16.0])
print(math.sqrt(areas))
TypeError: only length-1 arrays can be converted to Python scalars

✓ Right

import numpy as np

areas = np.array([4.0, 9.0, 16.0])
print(np.sqrt(areas))

The math module works on single numbers only (newer NumPy versions word this error "only 0-dimensional arrays can be converted to Python scalars"). Use the NumPy version, np.sqrt, which is applied to every element.

Shapes that cannot be broadcast

✗ Wrong

import numpy as np

sales = np.array([[10, 20, 30, 40],
                  [5, 15, 25, 35],
                  [1, 2, 3, 4]])
shop_totals = sales.sum(axis=1)
print(sales / shop_totals)
ValueError: operands could not be broadcast together with shapes (3,4) (3,)

✓ Right

import numpy as np

sales = np.array([[10, 20, 30, 40],
                  [5, 15, 25, 35],
                  [1, 2, 3, 4]])
shop_totals = sales.sum(axis=1, keepdims=True)
print(sales / shop_totals)

shop_totals has shape (3,), and compared from the right 4 and 3 do not match. Keep the summed axis as a length-1 dimension with keepdims=True (or [:, None]) so the shape is (3, 1) and each total is applied across its row.

Letting one nan ruin an average

✗ Wrong

import numpy as np

readings = np.array([21.5, np.nan, 22.5, 23.0])
print(readings.mean())
No error, but it prints nan

✓ Right

import numpy as np

readings = np.array([21.5, np.nan, 22.5, 23.0])
print(np.nanmean(readings))

Any calculation that touches a nan gives nan. Use the nan-aware versions such as np.nanmean, np.nansum or np.nanmax, or filter the missing values out first with readings[~np.isnan(readings)].

Overflow in a small integer type

✗ Wrong

import numpy as np

stock = np.array([100, 120], dtype=np.int8)
print(stock + stock)
No error, but it prints [-56 -16]: the sums wrapped around

✓ Right

import numpy as np

stock = np.array([100, 120], dtype=np.int8)
big = stock.astype(np.int32)
print(big + big)

An int8 can only hold -128 to 127, and NumPy does not warn when an array result does not fit. Convert to a bigger type with astype before doing the maths, or pick the dtype carefully when you create the array.

Using * when you wanted a matrix product

✗ Wrong

import numpy as np

quantities = np.array([[2, 1], [0, 3]])
prices = np.array([[1.5, 4.0], [2.0, 3.5]])
print(quantities * prices)
No error, but the result is element by element, not a matrix product

✓ Right

import numpy as np

quantities = np.array([[2, 1], [0, 3]])
prices = np.array([[1.5, 4.0], [2.0, 3.5]])
print(quantities @ prices)

* multiplies matching positions. For the row-times-column sums of a matrix product, use @ (or np.dot). Check that the inner sizes match: (2, 2) @ (2, 2) is fine.

9NumPy Math: exercises

Write your answer, then press Check. The checks run in your browser.

1. Balance after each year

Write code Easy

Write a function yearly_balances(start, rate, years) that returns a NumPy array with the balance at the end of each year, from year 1 to years, rounded to 2 decimal places. A balance grows by start * (1 + rate) ** year. Do it without a loop: build the years with np.arange and let NumPy do the power for all of them at once.

Your code
import numpy as np

def yearly_balances(start, rate, years):
    pass

print(yearly_balances(1000, 0.10, 3))

2. Predict axis, broadcasting and reshape

Predict the output Medium

Read the code and type exactly what it prints. NumPy prints arrays with spaces instead of commas, and a 2-D array on several lines.

Read this code
import numpy as np

grid = np.array([[1, 2, 3],
                 [4, 5, 6]])
print(grid.sum(axis=0))
print(grid.sum(axis=1))
print(grid + np.array([10, 20, 30]))
print(grid.reshape(3, 2)[:, 0])
print(grid.T.shape)

2 more exercises → 6 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

Questions learners ask

What is broadcasting in NumPy, in plain words?

Broadcasting is NumPy's rule for combining arrays of different shapes. It lines the shapes up from the right and stretches any dimension of size 1 (or a missing one) to match the other array, without copying the data. That is why a single row can be added to every row of a table. If the shapes cannot be made to match, you get a ValueError that lists both shapes.

Why is np.std different from the standard deviation in pandas or Excel?

np.std divides by n (the population standard deviation, ddof=0), while pandas .std() and Excel's STDEV divide by n - 1 (the sample standard deviation). Pass ddof=1 to np.std to match them: np.std(data, ddof=1). The gap is large for small samples and tiny for big ones.

What is the difference between ravel(), flatten() and reshape(-1)?

All three turn an array into 1-D. flatten() always returns a copy. ravel() and reshape(-1) return a view of the original data whenever they can, so editing the result may change the original. Use flatten() when you plan to modify the flat array and want the original left alone.

Further reading

10Next lesson

Up nextPandas Basicspandas is a library for working with tables: a DataFrame holds rows and named columns like a spreadsheet, and each colu…

Related:
Python math and statisticsNumPy BasicsNumPy Indexing and SlicingPandas Basics

← NumPy Indexing and Slicing · All Data Analysis lessons · Practise in the IDE