Math and Statistics
Core Python Functions and Modules

Python math and statistics

1Math and Statistics: overview

Python's built-in operators, the math module and the statistics module handle everyday calculations such as roots, rounding, logarithms, averages and spread, while Decimal and Fraction give exact answers where ordinary floats are only approximate.

GoalBy the end you can choose the right rounding function, compare floats safely with math.isclose, summarise a list with mean, median, mode and stdev, use Decimal for money, and read the errors that bad input produces.

Before this lesson: Python Operators, Python Modules and Imports

2Math and Statistics: diagram

Turning a number into a whole numberTurning a number into a whole numberxfloor(x)ceil(x)trunc(x)round(x)2.523223.534342.72323-2.5-3-2-2-2-2.7-3-2-2-30.50100round() sends an exact half to the even neighbour: round(2.5) is 2 but round(3.5) is 4
Four functions turn a decimal into a whole number, and they only agree on positive numbers that are not halves. Negative numbers and exact halves show the differences.

3Math and Statistics: explained step by step

Python can do arithmetic the moment you open it, but real programs soon need more: the long side of a triangle, the number of pages for 47 items, the average mark of a class, an invoice that adds up to the exact cent. This lesson covers the tools for each, and the places where plain decimal numbers (floats) let you down.

Built-in helpers: no import needed

Python already gives you abs() to drop the sign, round() to round, and divmod(a, b), which returns the quotient and the remainder in one call. That turns 135 minutes into hours and minutes.

Python
print(abs(-7.5))
print(round(3.14159, 2))
hours, minutes = divmod(135, 60)
print(hours, "h", minutes, "min")

The math module

import math unlocks the functions of a scientific calculator. You call each one as math.name(...), so a reader can see where it came from.

Python
import math

print(math.sqrt(144))
print(math.pi, math.e)
print(math.hypot(3, 4))
print(math.log10(1000), math.log2(8), math.log(math.e))
print(math.factorial(5), math.comb(5, 2), math.perm(5, 2))
print(math.gcd(12, 18), math.lcm(4, 6))

  • sqrt(x) is the square root. It always returns a float.
  • hypot(a, b) is the long side of a right-angled triangle, sqrt(a*a + b*b).
  • log(x) is the natural logarithm, log10 and log2 use base 10 and 2, and log(x, base) takes any base.
  • factorial(n) is n!. comb(n, k) counts the ways to choose k things out of n when order does not matter, and perm(n, k) when it does.
  • gcd is the greatest common divisor and lcm the lowest common multiple.

For powers, write 2 ** 3; math.pow(2, 3) always returns a float.

Angles are in radians

sin, cos and tan expect radians, not degrees. Convert with math.radians() on the way in and math.degrees() on the way out.

Python
import math

print(math.sin(90))
print(math.sin(math.radians(90)))
print(round(math.cos(math.radians(60)), 2))
print(math.degrees(math.pi))

The first line treats 90 as 90 radians, a different angle altogether.

Four ways to get a whole number

They treat the decimal part differently, and negative numbers show it best:

  • math.floor(x) always goes down, toward negative infinity.
  • math.ceil(x) always goes up. Use it for "how many boxes or pages do I need?".
  • math.trunc(x) (the same as int(x)) cuts the decimals off, which means toward zero.
  • round(x) picks the nearest whole number. An exact half goes to the even neighbour, so round(2.5) is 2 and round(3.5) is 4. This is banker's rounding: always rounding halves up would push long columns of figures upward, and going to the even side balances that out. A negative second argument rounds to tens or hundreds, so round(1234, -2) is 1200.
Python
import math

for x in (2.5, 3.5, -2.5):
    print(x, math.floor(x), math.ceil(x), math.trunc(x), round(x))
print(round(3.14159, 2), round(1234, -2))
print(math.ceil(47 / 10))

Floats are close, not exact

Computers store a float in binary, and a value such as 0.1 has no exact binary form. Tiny errors appear, and they surface when you compare: 0.1 + 0.2 == 0.3 is False. Use math.isclose() for floats.

Python
import math

total = 0.1 + 0.2
print(total == 0.3, total)
print(math.isclose(total, 0.3))
print(math.isclose(0, 1e-12))
print(math.isclose(0, 1e-12, abs_tol=1e-9))

By default isclose allows a small difference relative to the size of the numbers. Next to zero any relative difference is huge, so when one side may be 0 add abs_tol.

The statistics module

To summarise a list of numbers, use statistics instead of writing the formulas yourself.

Python
import statistics

marks = [62, 70, 70, 75, 81, 90, 98]
print(statistics.mean(marks))
print(statistics.median(marks), statistics.mode(marks))
print(statistics.multimode([1, 1, 2, 2, 3]))
print(round(statistics.stdev(marks), 2), round(statistics.pstdev(marks), 2))
print(statistics.quantiles(marks, n=4))

  • mean is the total divided by the count. median is the middle value once sorted, or the average of the two middle values when the count is even.
  • mode returns the most common value. multimode returns a list of all values that tie.
  • stdev measures how spread out the data is. Use it when your list is a sample of something bigger, and pstdev when it is the whole population. variance is the standard deviation squared.
  • quantiles(data, n=4) returns the three cut points that split the data into quarters.

The mean is easy to drag around, and the median is not: one huge value changes the mean a lot and the median hardly at all.

Python
import statistics

salaries = [30, 32, 35, 36, 400]
print(statistics.mean(salaries), statistics.median(salaries))

Exact numbers: Decimal and Fraction

For money, binary floats are the wrong tool. Decimal stores decimal digits exactly, so build it from a string. quantize() rounds to a chosen number of places, and you choose the rounding rule. Fraction keeps a ratio exact, so one third plus one sixth really is one half.

Python
from decimal import Decimal, ROUND_HALF_UP
from fractions import Fraction

print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3"))
print(Decimal(0.1))
print(round(2.675, 2), Decimal("2.675").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
print(Fraction(1, 3) + Fraction(1, 6))

Decimal(0.1) copies the float's error along with it. And round(2.675, 2) gives 2.67 because the float behind 2.675 sits a hair below it.

When the input is bad

A math function raises ValueError: math domain error when the maths has no answer, such as the square root of a negative number. The statistics functions raise StatisticsError (a kind of ValueError) when there is not enough data: mean([]), or stdev of a single value. Check the data first, or catch the error.

Python
import math
import statistics

for value in (16, -4):
    try:
        print(math.sqrt(value))
    except ValueError as error:
        print("Cannot take the root of", value, "->", error)

try:
    statistics.mean([])
except statistics.StatisticsError as error:
    print("No data:", error)

Which tool, when?

Operators and built-ins cover everyday sums. Use math for roots, logarithms, angles and rounding direction, statistics to summarise a list, Decimal for money and Fraction for exact ratios. For millions of numbers, switch to NumPy.

Quick reference: Math and Statistics

4Math and Statistics: syntax

import math
math.sqrt(x)  math.hypot(a, b)  math.pi  math.e
math.floor(x)  math.ceil(x)  math.trunc(x)  round(x, digits)
math.isclose(a, b, rel_tol=1e-09, abs_tol=0.0)
math.gcd(a, b)  math.lcm(a, b)  math.factorial(n)  math.comb(n, k)  math.perm(n, k)
math.log(x)  math.log10(x)  math.log2(x)  math.radians(deg)  math.degrees(rad)
divmod(a, b)                          # (a // b, a % b)

import statistics
statistics.mean(data)  statistics.median(data)  statistics.mode(data)
statistics.multimode(data)  statistics.stdev(data)  statistics.pstdev(data)
statistics.variance(data)  statistics.quantiles(data, n=4)

from decimal import Decimal, ROUND_HALF_UP
Decimal("19.99").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
from fractions import Fraction
Fraction(1, 3) + Fraction(1, 6)       # Fraction(1, 2)

5Math and Statistics: 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 math
import statistics

marks = [72, 85, 90, 66, 85, 78]

average = statistics.mean(marks)
middle = statistics.median(marks)
spread = statistics.stdev(marks)
print("Average:", round(average, 1))
print("Median:", middle)
print("Most common:", statistics.mode(marks))
print("Spread:", round(spread, 2))

top_third = math.ceil(len(marks) / 3)
best = sorted(marks, reverse=True)[:top_third]
print("Top", top_third, "marks:", best)
Expected outputAverage: 79.3
Median: 81.5
Most common: 85
Spread: 9.07
Top 2 marks: [90, 85]

7Math and Statistics: how the example works

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

  1. Line 1-2Two standard-library modules: math for ceil, and statistics for the averages.
  2. Line 4Six exam marks in a list. Notice that 85 appears twice.
  3. Line 6mean() adds the marks (476) and divides by how many there are (6), which gives 79.33...
  4. Line 7median() sorts the marks and, because the count is even, averages the two middle ones: 78 and 85 give 81.5.
  5. Line 8stdev() measures how far the marks are from the mean, treating the list as a sample of a bigger group.
  6. Line 9round(average, 1) only changes what is displayed. The variable average keeps its full precision.
  7. Line 10-11The median is printed as it is. mode() returns 85, the only value that occurs twice.
  8. Line 12The standard deviation is rounded to two decimals so the output stays tidy.
  9. Line 14math.ceil(6 / 3) is 2. Rounding up guarantees that nobody who belongs in the top third is left out when the class size does not divide evenly.
  10. Line 15sorted(..., reverse=True) makes a new list from highest to lowest, and the slice [:top_third] keeps the first two items.
  11. Line 16Prints the count and the winning marks.

8Math and Statistics: common mistakes

Taking the square root of a negative number

✗ Wrong

import math

profit = -4
print(math.sqrt(profit))
ValueError: math domain error

✓ Right

import math

profit = -4
if profit >= 0:
    print(math.sqrt(profit))
else:
    print("No real square root for", profit)

A real square root does not exist for a negative number, so math.sqrt refuses. Check the value first, or take abs() if the sign does not matter.

Comparing floats with ==

✗ Wrong

total = 0.1 + 0.2
if total == 0.3:
    print("Exact match")
else:
    print("No match:", total)
No error, but it prints No match: 0.30000000000000004

✓ Right

import math

total = 0.1 + 0.2
if math.isclose(total, 0.3):
    print("Exact match")
else:
    print("No match:", total)

Floats carry tiny binary rounding errors. Compare them with math.isclose() instead of ==.

Expecting round(2.5) to give 3

✗ Wrong

tip = 2.5
print(round(tip))
No error, but it prints 2, not 3

✓ Right

from decimal import Decimal, ROUND_HALF_UP

tip = Decimal("2.5")
print(tip.quantize(Decimal("1"), rounding=ROUND_HALF_UP))

round() sends an exact half to the nearest even number. If you need the school rule (halves go up), use Decimal with ROUND_HALF_UP.

Passing degrees to sin() and cos()

✗ Wrong

import math

print(math.sin(90))
No error, but it prints 0.8939966636005579 instead of 1.0

✓ Right

import math

print(math.sin(math.radians(90)))

The trigonometric functions work in radians. Convert degrees with math.radians() first.

Averaging an empty list

✗ Wrong

import statistics

scores = []
print(statistics.mean(scores))
statistics.StatisticsError: mean requires at least one data point

✓ Right

import statistics

scores = []
if scores:
    print(statistics.mean(scores))
else:
    print("No scores yet")

There is nothing to average. Check that the list has data before calling mean(), or catch the error and report that there are no scores yet.

9Math and Statistics: exercises

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

1. Circle area and ladder length

Write code Easy

Complete two functions with the math module:

  • circle_area(radius) returns the area of a circle (pi times the radius squared), rounded to 2 decimal places.
  • ladder_length(wall_height, distance) returns the length of a ladder that stands distance metres from a wall and touches it wall_height metres up. Use math.hypot, and round the result to 2 decimal places.

Both functions must return their answer.

Your code
import math

def circle_area(radius):
    pass

def ladder_length(wall_height, distance):
    pass

print(circle_area(2))
print(ladder_length(4, 3))

2. Predict the rounding

Predict the output Easy

Read the code and type exactly what it prints, line by line. Then press Check to run it and compare.

Read this code
import math

print(round(2.5), round(3.5), round(0.5))
print(math.floor(-2.5), math.ceil(-2.5), math.trunc(-2.5))
print(divmod(17, 5))
print(0.1 + 0.2 == 0.3, math.isclose(0.1 + 0.2, 0.3))

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

Why does 0.1 + 0.2 give 0.30000000000000004 in Python?

Python floats are stored in binary, and numbers like 0.1 and 0.2 cannot be written exactly in binary, just as 1/3 cannot be written exactly in decimal. Each one is stored a tiny bit off, and the error shows when you add them. Every language that uses standard floats does this. Round for display, compare with math.isclose(), and use Decimal for money.

Why does round(2.5) return 2 in Python?

Python 3 uses banker's rounding: an exact half goes to the nearest even whole number, so round(2.5) is 2 and round(3.5) is 4. Over many numbers this avoids always pushing totals upward. If you need halves to go up, use Decimal("2.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP). Note that round(2.675, 2) gives 2.67 for a different reason: the float is stored slightly below 2.675.

What is the difference between statistics.stdev and statistics.pstdev?

stdev is the sample standard deviation: it divides by n - 1, and you use it when your list is only a sample taken from a larger group. pstdev is the population standard deviation: it divides by n, and you use it when the list contains every value you care about. For the same data, stdev is always slightly larger, and it needs at least two values.

Further reading

10Next lesson

Up nextPython random ModuleThe random module draws pseudo-random numbers and picks from a seeded generator, so you can simulate dice, shuffle and …

Related:
Python OperatorsPython Modules and ImportsPython random ModuleNumPy Basics

← Python Modules and Imports · All Functions and Modules lessons · Practise in the IDE