Class 11Computer Science · Programming with PythonFull chapter

Python Modules

The whole chapter in one place — read it, then test yourself. Clear notes, a reference sheet, a practice quiz, and worked NCERT solutions & PYQs.

What a Module Is, and the import Statement

Quick answer A module is a file of ready-written, tested Python code; import loads it, and after a plain import every name inside it must be reached through dot notation.

A module is a file of ready-written Python code that you can pull into your own program. Somebody has already written it, tested it and packed it up; you just borrow it. Python ships with dozens of these. Your syllabus names three: math, random and statistics.

A note about your NCERT book, before you get confused. NCERT prints all of this material inside Chapter 7, which is titled "Functions". Most of that chapter teaches you to write your own functions with def and return, along with parameters and scope. The CBSE 2025-26 syllabus for Class 11 (code 083) places all of that in Class 12, not Class 11. Only the module half of NCERT Chapter 7 is examinable for you this year. So when you open the book and find pages on def, do not panic that we have skipped them - they are not yours yet. Everything on this page is.

Why modules exist at all. Suppose there were no math module and you needed a square root. You would have to write the algorithm yourself. Here is the Babylonian method, which starts from a guess and keeps improving it:

# Square root the hard way (Babylonian method)
n = 2
guess = 1.0
for i in range(20):
    guess = (guess + n / guess) / 2
print("Hand written :", guess)

# Square root the module way - one line
import math
print("math.sqrt(2)  :", math.sqrt(2))

Output:

Hand written : 1.414213562373095
math.sqrt(2)  : 1.4142135623730951

Five lines and a loop against one line - and look closely at the last digit. The module's answer is the more accurate of the two, because it hands the job to the processor's own square-root instruction instead of guessing twenty times. That is the whole argument for modules: code that is correct, fast and already tested, which you did not have to write.

Nothing is available until you import it. Run this on its own:

print(sqrt(144))

The last lines of the traceback read:

NameError: name 'sqrt' is not defined

Now add the import and run it again:

import math
print(sqrt(144))

It still fails, with exactly the same message:

NameError: name 'sqrt' is not defined

This trips up almost everybody once. import math brings one new name into your program: math. It does not scatter sqrt, pi, floor and the rest loose in your file. Everything inside has to be reached through the module's name, with a dot:

import math

print(math.sqrt(144))
print(math.pi)
print(type(math))
12.0
3.141592653589793

The dot means "inside". Read math.sqrt as "the sqrt that lives inside math". Notice also that math has a type of its own - module - just as 7 has type int.

Giving a module a nickname. If a module name is long or you use it constantly, import ... as renames it:

import math as m

side = 7
print("Area of square plot :", m.pow(side, 2), "sq m")
print("Diagonal of the plot:", round(m.sqrt(2 * side * side), 2), "m")
Area of square plot : 49.0 sq m
Diagonal of the plot: 9.9 m

After import math as m, the name math does not work - only m does. The nickname replaces the original, it does not sit alongside it, so math.sqrt(4) would raise NameError: name 'math' is not defined.

Seeing what is inside a module. The built-in dir() lists every name a module offers:

import math
print(dir(math)[:8])
print(len(dir(math)))
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin']
67

Sixty-seven names in one small module on Python 3.13 (the exact count varies a little between versions). You only need the nine your syllabus lists, but dir() is genuinely useful when you are stuck and cannot remember a spelling.

import import math Loads the whole module. Adds only the name 'math' to your program.
Dot notation module.item math.sqrt(144), math.pi. The dot means 'inside'. Without it you get a NameError.
import ... as import math as m Nickname. After this m.sqrt(4) works and math.sqrt(4) does NOT - the alias replaces the name.
Several modules at once import math, random Legal. One import per line is the usual style and is what board answers normally show.
dir() dir(math) -> list of strings Built-in. Lists every name the module offers. len(dir(math)) is 67 on Python 3.13; the count varies slightly between versions.
Missing import sqrt(144) # with no import math NameError: name 'sqrt' is not defined. The same error appears if you import math but forget the math. prefix.
Remember
  • A module is a file of ready-written Python code; importing it lets you reuse work you did not write, correctly and fast.
  • NCERT prints modules inside Chapter 7 ("Functions"), but CBSE places user-defined functions in the Class 12 syllabus - only the module part of that chapter is in your Class 11 course.
  • import math adds exactly one name to your program: math. Every item inside must then be written with dot notation, as math.sqrt(144).
  • Forgetting the import, or forgetting the dot, both give the same error: NameError: name 'sqrt' is not defined.
  • import math as m renames the module - after it, m works and math does not.

The from Statement and the Import Forms

Quick answer from module import name pulls chosen names straight into your program so you can use them without a prefix, while from module import * pulls in everything and quietly risks one name overwriting another.

Typing math. in front of everything gets tiring. The from statement lets you pull chosen names straight into your own program, so no prefix is needed.

from math import sqrt, pi

print(sqrt(625))
print(pi)
25.0
3.141592653589793

Now here is the trade-off. from math import sqrt, pi brings in sqrt and pi - and nothing else, not even the name math. So this fails:

from math import sqrt, pi
print(math.sqrt(625))

The last line of the traceback:

NameError: name 'math' is not defined. Did you forget to import 'math'?

Python's own hint is slightly misleading here - you did import from math, you just never asked for the module object itself. Remember it as a rule: plain import gives you the module; from gives you the pieces. They are opposite ends of the same trade.

Renaming on the way in. as works with from too:

from statistics import mean as average
from math import sqrt as root

marks = [72, 65, 88, 91, 54]
print("Average marks:", average(marks))
print("Root of 81   :", root(81))
Average marks: 74
Root of 81   : 9.0

Importing everything with *. The star means "every public name in this module":

from math import *
from statistics import *

print(floor(4.9), ceil(4.1))
print(mean([10, 20, 30]))
4 5
20

Convenient - and this is where students lose marks. When you dump every name from a module into your program, a name can silently overwrite one that was already there. Watch:

print(pow(2, 10))          # built-in pow
from math import *
print(pow(2, 10))          # math.pow has now taken over the name
1024
1024.0

Same call, same arguments, different answer. Python has a built-in pow() that returns an int; math also has a pow(), and it always returns a float. The star import replaced one with the other, without a single warning. In a program that later did bill[pow(2, 3)], that 8.0 would stop the program with TypeError: list indices must be integers or slices, not float.

The reverse happens too - your own variable can wipe out an imported function:

from math import pow
from statistics import mean

pow = 5          # our own variable, same name
print(pow)
print(pow(2, 3)) # Python now looks at the number 5
5
TypeError: 'int' object is not callable

"'int' object is not callable" always means the same thing: you put brackets after something that is not a function. Here it is the integer 5, because pow stopped being a function the moment you assigned to it. This is why the safe habit - and the one to use in an exam unless the question says otherwise - is plain import math with the dot prefix. It makes the source of every name obvious and nothing can be overwritten by accident.

Worked example - a shop bill using all three forms.

import math, random          # two modules on one line
from statistics import mean  # one name pulled out of a third

random.seed(42)
bill = [249.50, 1199.00, 75.25, 640.75]

print("Items         :", bill)
print("Total (Rs)    :", sum(bill))
print("Rounded up    : Rs", math.ceil(sum(bill)))
print("Average item  : Rs", round(mean(bill), 2))
print("Lucky coupon  :", random.randint(1000, 9999))
Items         : [249.5, 1199.0, 75.25, 640.75]
Total (Rs)    : 2164.5
Rounded up    : Rs 2165
Average item  : Rs 541.12
Lucky coupon  : 2824

math and random keep their prefixes; mean was pulled out and is used bare. Both styles happily coexist in one file. (random.seed(42) is there so this page prints the same coupon number every time - more on that in the random section.)

from ... import from math import sqrt, pi Only those names come in, used bare: sqrt(625). math.sqrt(625) now raises NameError.
from ... import * from math import * Every public name comes in. Convenient but unsafe - names can overwrite each other with no warning.
from ... import ... as from statistics import mean as average Renames on the way in. average(marks) works; mean(marks) does not.
Overwriting by star import pow(2,10) -> 1024 then from math import * then pow(2,10) -> 1024.0 math.pow replaced the built-in pow. Same call, different type of answer.
Overwriting by assignment pow = 5 ; pow(2, 3) TypeError: 'int' object is not callable. Never name a variable after a function you imported.
Safest form for exams import math then math.sqrt(x) The prefix shows where every name came from and nothing can be shadowed.
Remember
  • from math import sqrt, pi brings in only the named items, used with no prefix; the name math itself is NOT brought in.
  • Plain import gives you the module; from gives you the pieces. You cannot use math.sqrt() after a from import unless you also imported the module.
  • from math import * imports every public name and can silently overwrite an existing one - after it, pow(2, 10) gives 1024.0 instead of 1024.
  • Assigning to a name you imported destroys it: pow = 5 then pow(2, 3) raises TypeError: 'int' object is not callable.
  • Both forms accept as for renaming, and both styles can be mixed in the same program.

The math Module

Quick answer math supplies the constants pi and e plus sqrt(), ceil(), floor(), pow(), fabs() and the trigonometric sin(), cos(), tan(), which take their angle in radians and not degrees.

The math module holds the number-crunching tools. Your syllabus names exactly nine items from it, and every one is below with its real output.

The two constants. pi and e are values, not functions - no brackets. Writing math.pi() is an error.

import math

print("pi   =", math.pi)
print("e    =", math.e)
print("sqrt(2)   =", math.sqrt(2))
print("ceil(4.1) =", math.ceil(4.1))
print("floor(4.9)=", math.floor(4.9))
print("ceil(-4.1)  =", math.ceil(-4.1))
print("floor(-4.1) =", math.floor(-4.1))
print("pow(2, 5) =", math.pow(2, 5))
print("fabs(-7)  =", math.fabs(-7))
pi   = 3.141592653589793
e    = 2.718281828459045
sqrt(2)   = 1.4142135623730951
ceil(4.1) = 5
floor(4.9)= 4
ceil(-4.1)  = -4
floor(-4.1) = -5
pow(2, 5) = 32.0
fabs(-7)  = 7.0

Read the negative cases carefully. ceil(-4.1) is -4, not -5. ceil always moves up the number line and floor always moves down. On the number line -4 is above -4.1. Students who think of ceil as "round away from zero" get this wrong every single time. Nor is ceil the same as int(): int() chops towards zero, so int(-4.1) gives -4 (which matches ceil by luck) but int(4.9) gives 4 while math.ceil(4.9) gives 5. Keep the picture of the number line, not of zero.

xmath.ceil(x)math.floor(x)
4.154
4.954
-4.1-4-5
-2.5-2-3

math.fabs vs abs, math.pow vs pow. Python already has built-in abs() and pow(). The math versions do the same arithmetic but always hand back a float:

import math

print(math.fabs(-7), abs(-7))
print(math.pow(2, 5), pow(2, 5), 2 ** 5)
print(type(math.fabs(-7)), type(abs(-7)))
7.0 7
32.0 32 32
 

Three ways to get 32, and only math.pow gives you 32.0. Board questions love this difference, so remember: of the nine math items in your syllabus, everything returns a float except ceil() and floor(), which return int. (The wider module does contain a few other int-returning functions, such as factorial() and gcd(), but those are not in your syllabus and will not be asked.)

Trigonometry - the angle is in radians. This is the single biggest mistake with sin, cos and tan. They do not take degrees.

import math

print("sin(0)        =", math.sin(0))
print("cos(0)        =", math.cos(0))
print("tan(0)        =", math.tan(0))
print("sin(pi/2)     =", math.sin(math.pi / 2))
print("cos(pi/2)     =", math.cos(math.pi / 2))
print("tan(pi/4)     =", math.tan(math.pi / 4))
sin(0)        = 0.0
cos(0)        = 1.0
tan(0)        = 0.0
sin(pi/2)     = 1.0
cos(pi/2)     = 6.123233995736766e-17
tan(pi/4)     = 0.9999999999999999

Two of those look wrong and are not. cos(pi/2) should be exactly 0, and it prints 6.123233995736766e-17 - that is 0.0000000000000000612, effectively zero. tan(pi/4) should be exactly 1 and prints 0.9999999999999999. The reason is that math.pi is itself a rounded-off decimal; pi cannot be stored exactly in binary, so pi/2 is a hair off the true right angle. In real programs you wrap such results in round().

Converting degrees to radians. Multiply by math.pi / 180:

import math

deg = 30
rad = deg * math.pi / 180
print("30 degrees =", rad, "radians")
print("sin(30) =", round(math.sin(rad), 4))
print("cos(60) =", round(math.cos(60 * math.pi / 180), 4))
print("tan(45) =", round(math.tan(45 * math.pi / 180), 4))
30 degrees = 0.5235987755982988 radians
sin(30) = 0.5
cos(60) = 0.5
tan(45) = 1.0

Those are the values you learnt in Maths, so the conversion is right.

Worked example - buying a ladder. A ladder leans against a wall at 65 degrees and its foot is 2.5 m from the wall. How long is the ladder, how high does it reach, and what size should you buy?

import math

angle_deg = 65
foot = 2.5
angle_rad = angle_deg * math.pi / 180

length = foot / math.cos(angle_rad)
height = length * math.sin(angle_rad)

print("Ladder length :", round(length, 2), "m")
print("Wall height   :", round(height, 2), "m")
print("Buy a ladder of at least", math.ceil(length), "m")
Ladder length : 5.92 m
Wall height   : 5.36 m
Buy a ladder of at least 6 m

math.ceil is exactly right for the last line. You cannot buy 5.92 m of ladder, and rounding down to 5 m would leave you short. Whenever the real-world answer is "you need at least this much", reach for ceil.

math.pi and math.e math.pi -> 3.141592653589793 ; math.e -> 2.718281828459045 Constants, not functions. math.pi() raises TypeError: 'float' object is not callable.
math.sqrt() math.sqrt(x) -> float math.sqrt(144) is 12.0, never 12. A negative argument raises ValueError: math domain error.
math.ceil() and math.floor() math.ceil(x) -> int ; math.floor(x) -> int The only two syllabus math functions that return int. ceil(-2.5) = -2, floor(-2.5) = -3.
math.pow() math.pow(x, y) -> float Always float. Built-in pow(2,5) gives 32 but math.pow(2,5) gives 32.0.
math.fabs() math.fabs(x) -> float Absolute value as float. abs(-7) is 7 (int), math.fabs(-7) is 7.0 (float).
math.sin() / cos() / tan() math.sin(x) -> float, where x is in RADIANS Degrees must be converted: rad = deg * math.pi / 180. Results may carry tiny errors, e.g. tan(pi/4) = 0.9999999999999999.
Remember
  • math.pi (3.141592653589793) and math.e (2.718281828459045) are constants - no brackets.
  • ceil() and floor() return int; sqrt(), pow() and fabs() return float. So math.pow(2,5) is 32.0 while built-in pow(2,5) is 32.
  • ceil moves up the number line and floor moves down, so ceil(-4.1) is -4 and floor(-4.1) is -5.
  • sin(), cos() and tan() take radians. Convert with deg * math.pi / 180 before calling them.
  • Trig results are not always exact - cos(math.pi/2) prints 6.123233995736766e-17, not 0.0, because math.pi is itself a rounded value.

The random Module

Quick answer random() returns a float in [0.0, 1.0), randint(a, b) returns an integer including both ends, and randrange(start, stop, step) follows range() rules and excludes the stop value.

The random module produces numbers you cannot predict - for dice games, OTPs, shuffled question papers, lucky-draw coupons. Your syllabus names three functions: random(), randint() and randrange().

Before anything else: output changes every run. If you run any snippet in this section on your own machine you will get different numbers, and that is correct behaviour, not a mistake. To print stable outputs on this page, most snippets below start with random.seed(n), which fixes the starting point of the number generator so the same sequence comes out every time. seed() is not in your syllabus list - it is used here purely so you can check the printed outputs against your own run. Remove the seed line and the numbers become genuinely unpredictable again.

Proof that a seed really does pin the sequence - this file was run twice and gave identical output both times:

import random

random.seed(10)          # fixes the sequence
print(random.random())
print(random.random())
print(random.randint(1, 6))
print(random.randrange(1, 6))
print(random.randrange(0, 50, 10))
0.5714025946899135
0.4288890546751146
5
1
10

random() - a float between 0 and 1. It takes no arguments. The range is written [0.0, 1.0): the square bracket means 0.0 can come out, the round bracket means 1.0 never does. Two hundred thousand calls confirm it:

import random
random.seed(0)
lo = 1.0
hi = 0.0
for i in range(200000):
    r = random.random()
    if r < lo:
        lo = r
    if r > hi:
        hi = r
print("lowest seen :", lo)
print("highest seen:", hi)
print("Was 1.0 ever produced?", hi == 1.0)
lowest seen : 1.224005981570464e-05
highest seen: 0.9999965148287956
Was 1.0 ever produced? False

On its own a number between 0 and 1 is not much use, so you scale it:

import random

random.seed(7)
r = random.random()
print("random()          :", r)
print("scaled to 0-100   :", r * 100)
print("marks out of 100  :", int(r * 100))
print("1 to 6 the hard way:", int(random.random() * 6) + 1)
random()          : 0.32383276483316237
scaled to 0-100   : 32.38327648331624
marks out of 100  : 32
1 to 6 the hard way: 1

randint(a, b) - both ends included. This is the one that behaves differently from everything else in Python, so it is worth burning in. randint(1, 5) really can give you 5.

randrange(start, stop) - stop excluded. It follows exactly the same rules as range(), which you already know from loops. randrange(1, 5) can never give 5.

Here they are side by side, each called a thousand times, with every new value collected into a list:

import random

random.seed(1)
a = []
b = []
for i in range(1000):
    x = random.randint(1, 5)
    if x not in a:
        a.append(x)
    y = random.randrange(1, 5)
    if y not in b:
        b.append(y)

print("randint(1, 5)   produced:", sorted(a))
print("randrange(1, 5) produced:", sorted(b))
randint(1, 5)   produced: [1, 2, 3, 4, 5]
randrange(1, 5) produced: [1, 2, 3, 4]

That is the whole difference, and it is worth a mark in almost every paper.

randrange with one argument, and with a step. Again, identical to range():

import random

random.seed(5)
vals = []
for i in range(500):
    v = random.randrange(3)
    if v not in vals:
        vals.append(v)
print("randrange(3) gives:", sorted(vals))

vals2 = []
for i in range(2000):
    v = random.randrange(5, 30, 5)
    if v not in vals2:
        vals2.append(v)
print("randrange(5, 30, 5) gives:", sorted(vals2))
randrange(3) gives: [0, 1, 2]
randrange(5, 30, 5) gives: [5, 10, 15, 20, 25]

Note that 30 never appears - the stop value is excluded even when a step is given.

Worked example - a UPI-style OTP and a seat allotment.

import random

random.seed(2026)
otp = ""
for i in range(6):
    otp = otp + str(random.randint(0, 9))
print("Your UPI OTP is", otp)

# a step size: even seat numbers from 0 to 18, since 20 is excluded
random.seed(2026)
print("Seat allotted:", random.randrange(0, 20, 2))
Your UPI OTP is 158813
Seat allotted: 2

randint(0, 9) is used because a digit of an OTP must be able to be 9. If you wrote randrange(0, 9) the stop value would be excluded and your OTP could never contain a 9. That is not merely cosmetic: the number of possible OTPs would fall from 10 ** 6 = 1000000 to 9 ** 6 = 531441, so the code would be almost twice as easy to guess.

Worked example - picking a random item from a list. The index must be a valid position, so the range runs from 0 to len(list) - 1:

import random
CITY = ["DELHI", "MUMBAI", "CHENNAI", "KOLKATA"]
random.seed(3)
for i in range(4):
    n = random.randint(0, 3)
    print(CITY[n], end="  ")
print()
MUMBAI  MUMBAI  CHENNAI  KOLKATA

With four cities you need indexes 0, 1, 2, 3 - so randint(0, 3) or, equivalently, randrange(0, 4). Getting that boundary wrong is the classic IndexError, and it is exactly what the board's "possible output" questions are testing.

random.random() random.random() -> float, 0.0 <= n < 1.0 No arguments. Scale it with multiplication, e.g. int(random.random()*6)+1 for a dice.
random.randint() random.randint(a, b) -> int, a <= n <= b BOTH ends included. This is the odd one out in Python - everything else excludes the upper end.
random.randrange() random.randrange(start, stop) -> int, start <= n < stop Upper end EXCLUDED, exactly like range(). randrange(1,5) gives 1,2,3,4 only.
randrange() with step random.randrange(start, stop, step) randrange(5, 30, 5) gives 5, 10, 15, 20, 25. 30 is never produced.
randrange() with one argument random.randrange(n) -> 0 to n-1 randrange(3) gives 0, 1 or 2. Start defaults to 0.
random.seed() random.seed(n) Fixes the sequence so the same numbers repeat every run. Used on this page for stable printed output; not in the Class 11 syllabus list.
Remember
  • Output differs on every run. Seeds are used on this page (random.seed(n)) only so the printed outputs are reproducible; without a seed the values are unpredictable.
  • random.random() takes no arguments and returns a float in [0.0, 1.0) - 0.0 is possible, 1.0 never is.
  • random.randint(a, b) includes BOTH a and b. randint(1, 5) can return 5.
  • random.randrange(start, stop, step) follows range() rules - the stop value is EXCLUDED. randrange(1, 5) never returns 5, and randrange(5, 30, 5) tops out at 25.
  • To pick a random item from a list of n elements, use randint(0, n-1) or randrange(0, n) - both give valid indexes.

The statistics Module

Quick answer statistics gives mean(), median() and mode() straight out of the box, with the traps being that median of an even-length list is a float and mode returns the first-occurring value when there is a tie.

Averages come up constantly - class results, cricket scores, monthly bills. The statistics module gives you the three measures of central tendency without writing a line of arithmetic. Your syllabus names mean(), median() and mode().

import statistics

marks = [78, 65, 92, 65, 88]
print("mean  :", statistics.mean(marks))
print("median:", statistics.median(marks))
print("mode  :", statistics.mode(marks))
print("sorted:", sorted(marks))
mean  : 77.6
median: 78
mode  : 65
sorted: [65, 65, 78, 88, 92]

Check it by hand. Mean = (78+65+92+65+88)/5 = 388/5 = 77.6. For the median the list has to be in order - [65, 65, 78, 88, 92] - and the middle of five values is the third, which is 78. You do not have to sort the list yourself; median() sorts internally. The mode is 65 because it appears twice and everything else appears once.

The even-length trap. When the count is even there is no single middle value, so median() averages the two middle ones - and an average of two integers is a float:

import statistics

a = [10, 20, 30, 40]
print("median of", a, "=", statistics.median(a))
b = [10, 20, 30]
print("median of", b, "=", statistics.median(b))
print("mean of", a, "=", statistics.mean(a))
print("mean of [1, 2] =", statistics.mean([1, 2]))
print("mean of [1, 2, 4] =", statistics.mean([1, 2, 4]))
median of [10, 20, 30, 40] = 25.0
median of [10, 20, 30] = 20
mean of [10, 20, 30, 40] = 25
mean of [1, 2] = 1.5
mean of [1, 2, 4] = 2.3333333333333335

Look at the first and third lines together. For the same list [10, 20, 30, 40], the median prints 25.0 and the mean prints 25. That is not a typo:

import statistics

print(type(statistics.mean([10, 20, 30, 40])), statistics.mean([10, 20, 30, 40]))
print(type(statistics.median([10, 20, 30, 40])), statistics.median([10, 20, 30, 40]))
print(type(statistics.median([10, 20, 30])), statistics.median([10, 20, 30]))
 25
 25.0
 20

The rule is: mean() returns an int when the division comes out exact and all the data are ints, otherwise a float. median() returns one of the actual data values when the count is odd (so int here), and a computed average when the count is even (so float). If a question shows an even-length list, write the median with the .0.

mode() and ties. mode() returns the most frequent value. It works on strings too, not just numbers:

import statistics

print("mode of [1, 1, 2, 2, 3] =", statistics.mode([1, 1, 2, 2, 3]))
print("mode of ['pen','book','pen','bag'] =", statistics.mode(['pen', 'book', 'pen', 'bag']))
print("mode of a tuple (4, 4, 9) =", statistics.mode((4, 4, 9)))
mode of [1, 1, 2, 2, 3] = 1
mode of ['pen','book','pen','bag'] = pen
mode of a tuple (4, 4, 9) = 4

In the first list, 1 and 2 both appear twice. From Python 3.8 onwards mode() no longer complains about a tie - it returns whichever of the tied values appears first in the data, which here is 1. Older books say this raises StatisticsError; that was true up to Python 3.7 and is no longer the behaviour. Also note that all three functions accept any sequence - list, tuple or range - not just a list.

Empty data is an error. There is no average of nothing:

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

Worked example - a class report card. This one pulls in math as well, which is how these modules actually get used:

import statistics
import math

names = ["Aarav", "Diya", "Kabir", "Meera", "Rohan", "Sana"]
marks = [88, 72, 88, 57, 95, 72]

print("Name   : Marks")
for i in range(len(names)):
    print(names[i], ":", marks[i])

print()
print("Class average :", statistics.mean(marks))
print("Rounded (2 dp):", round(statistics.mean(marks), 2))
print("Median        :", statistics.median(marks))
print("Mode          :", statistics.mode(marks))
print("Highest       :", max(marks))
print("Lowest        :", min(marks))
print("Average shown on report card:", math.ceil(statistics.mean(marks)))
Name   : Marks
Aarav : 88
Diya : 72
Kabir : 88
Meera : 57
Rohan : 95
Sana : 72

Class average : 78.66666666666667
Rounded (2 dp): 78.67
Median        : 80.0
Mode          : 88
Highest       : 95
Lowest        : 57
Average shown on report card: 79

Three things to notice. The median is 80.0 because six is an even count - sorted the marks are [57, 72, 72, 88, 88, 95] and (72+88)/2 = 80.0. The mode is 88 even though 72 also appears twice, because 88 comes first in the original list. And max() and min() are ordinary built-ins - they need no import at all, so do not go looking for them in statistics.

statistics.mean() statistics.mean(data) -> int or float Sum divided by count. mean([10,20,30,40]) -> 25 (int); mean([1,2]) -> 1.5 (float).
statistics.median() statistics.median(data) Sorts internally. Odd count -> the middle value; even count -> average of the two middle values, always a float.
statistics.mode() statistics.mode(data) Most frequent value. On a tie returns the one occurring FIRST in the data (Python 3.8+). Works on strings too.
Accepted data types mean((4, 4, 9)) mode(['pen','bag','pen']) Lists, tuples and ranges all work. mean and median need numbers; mode accepts any comparable values.
Empty data statistics.mean([]) StatisticsError: mean requires at least one data point.
Import styles import statistics / from statistics import mean, median, mode Both examinable. The second lets you write mean(x) with no prefix, but then statistics.mean(x) will fail.
Remember
  • statistics.mean(), median() and mode() accept any sequence - list, tuple or range - and median() sorts the data internally, so you need not sort it first.
  • median() of an EVEN-length list is the average of the two middle values and prints as a float: median([10,20,30,40]) is 25.0.
  • mean() returns an int when the division is exact on integer data (mean([10,20,30,40]) is 25) and a float otherwise.
  • On a tie, mode() returns the value that occurs FIRST in the data (Python 3.8 onwards). It works on strings as well as numbers.
  • Empty data raises statistics.StatisticsError. max() and min() are built-ins and need no import.

The formula sheet

Every formula in this chapter, in one place — screenshot it before your exam.

import math
import
module.item
Dot notation
import math as m
import ... as
import math, random
Several modules at once
dir(math) -> list of strings
dir()
sqrt(144) # with no import math
Missing import
from math import sqrt, pi
from ... import
from math import *
from ... import *
from statistics import mean as average
from ... import ... as
pow(2,10) -> 1024 then from math import * then pow(2,10) -> 1024.0
Overwriting by star import
pow = 5 ; pow(2, 3)
Overwriting by assignment
import math then math.sqrt(x)
Safest form for exams
math.pi -> 3.141592653589793 ; math.e -> 2.718281828459045
math.pi and math.e
math.sqrt(x) -> float
math.sqrt()
math.ceil(x) -> int ; math.floor(x) -> int
math.ceil() and math.floor()
math.pow(x, y) -> float
math.pow()
math.fabs(x) -> float
math.fabs()
math.sin(x) -> float, where x is in RADIANS
math.sin() / cos() / tan()
random.random() -> float, 0.0 <= n < 1.0
random.random()
random.randint(a, b) -> int, a <= n <= b
random.randint()
random.randrange(start, stop) -> int, start <= n < stop
random.randrange()
random.randrange(start, stop, step)
randrange() with step
random.randrange(n) -> 0 to n-1
randrange() with one argument
random.seed(n)
random.seed()
statistics.mean(data) -> int or float
statistics.mean()
statistics.median(data)
statistics.median()
statistics.mode(data)
statistics.mode()
mean((4, 4, 9)) mode(['pen','bag','pen'])
Accepted data types
statistics.mean([])
Empty data
import statistics / from statistics import mean, median, mode
Import styles

Test yourself

Tap an answer to check it instantly — you'll see why it's right, and what to revise if it isn't.

0 correct · 0/12 answered
Q1

Predict the output:import mathprint(math.floor(-2.5), math.ceil(-2.5))

Q2

Predict the output:import mathprint(math.pow(3, 2) + math.fabs(-3))

Q3

Predict the output:from math import sqrtprint(sqrt(16) + 16 ** 0.5)

Q4

Predict the output:import statisticsd = [4, 4, 6, 8]print(statistics.mean(d), statistics.median(d), statistics.mode(d))

Q5

Predict the output:print(pow(2, 10))from math import *print(pow(2, 10))

Q6

Which value can random.randrange(5, 30, 5) NEVER return?

Q7

Which set of values can random.randint(1, 5) produce?

Q8

Predict the output:from math import sqrt, piprint(math.sqrt(625))

Q9

Predict the output:import math as mprint(m.ceil(m.sqrt(50)), m.floor(m.sqrt(50)))

Q10

After the single line import math, which statement runs without error?

Q11

Predict the output:import statisticsprint(statistics.median([7, 3, 9, 1]))

Q12

Predict the output:import mathx = math.pow(2, 3)y = pow(2, 3)print(type(x).__name__, type(y).__name__)

NCERT solutions & previous-year questions

Step-by-step model answers — tap a question to reveal the full solution.

NCERT questions 6

1 What is a module in Python? Why are modules used?Modules - concept

A module is a file containing Python code - definitions, statements and ready-made functions - that can be brought into another program with the import statement. Python comes with a large collection of such modules, called the standard library; math, random and statistics are three of them.

Why modules are used:

  1. Reusability - code written and tested once can be used in any number of programs without being retyped.
  2. Less code, fewer bugs - a single line replaces an algorithm you would otherwise have to write and debug yourself.
  3. Reliability and speed - standard-library code is written by experts and tested by millions of users; math in particular uses the processor's own arithmetic instructions.
  4. Organisation - related work is grouped together, so all numeric tools sit in math and all averaging tools sit in statistics.
  5. Easy maintenance - if the module is improved, every program that imports it benefits without being edited.

Demonstration:

import math
import random
import statistics

print("math is a", type(math))
print("Work done by other people, used in one line each:")
print(" ", math.sqrt(169))
print(" ", statistics.mean([10, 20, 30, 45]))
random.seed(1)
print(" ", random.randint(1, 100))

Output:

math is a 
Work done by other people, used in one line each:
  13.0
  26.25
  18

Each of those three results - a square root, an average and a random number - would take several lines of your own code. Note that a module has a type of its own, module, just as a number has type int. (The seed is used only so the random number printed here can be reproduced; without it that line changes every run.)

2 What are the different ways of importing a module in Python? Show each one with an example, and state the difference between them.import and from statements

There are four forms. All four are shown below in one program, each producing the same square root of 49:

# Form 1
import math
print("Form 1:", math.sqrt(49))

# Form 2
import math as m
print("Form 2:", m.sqrt(49))

# Form 3
from math import sqrt
print("Form 3:", sqrt(49))

# Form 4
from math import *
print("Form 4:", sqrt(49), floor(4.7))

Output:

Form 1: 7.0
Form 2: 7.0
Form 3: 7.0
Form 4: 7.0 4
FormWhat it brings inHow you call it
import mathOnly the name mathmath.sqrt(49)
import math as mOnly the name mm.sqrt(49); the name math is never created
from math import sqrtOnly the name sqrtsqrt(49); math.sqrt() fails
from math import *Every public name in the modulesqrt(49), floor(4.7), etc.

The key difference. A plain import gives you the module object, so everything inside must be reached with a dot. A from import gives you the individual pieces, used with no prefix, but does not give you the module name at all. Proof:

from math import sqrt, pi
print(math.sqrt(625))
NameError: name 'math' is not defined. Did you forget to import 'math'?

Which to prefer. import math is the safest, because the math. prefix shows exactly where each name came from and nothing can be accidentally overwritten. from math import * is the riskiest - it can silently replace an existing name:

print(pow(2, 10))          # built-in pow
from math import *
print(pow(2, 10))          # math.pow has now taken over the name
1024
1024.0
3 Write a program that reads a number from the user and uses the math module to display its square root, ceiling value, floor value and absolute value.math module

Program:

import math

num = float(input("Enter a number: "))
print("Square root :", math.sqrt(num))
print("Ceiling     :", math.ceil(num))
print("Floor       :", math.floor(num))
print("Absolute    :", math.fabs(num))

Output (with 72.4 typed at the prompt):

Enter a number: 72.4
Square root : 8.508818954473059
Ceiling     : 73
Floor       : 72
Absolute    : 72.4

Points to note in the answer:

  • float(input(...)) is needed because input() always returns a string, and math.sqrt("72.4") would raise TypeError: must be real number, not str.
  • math.sqrt() returns a float even for a perfect square - math.sqrt(49) is 7.0, not 7.
  • math.ceil() and math.floor() return int, which is why 73 and 72 print without a decimal point.
  • math.fabs() returns a float, so a negative input like -72.4 would print 72.4. The built-in abs() would give the same value but keep an int input as an int.
  • If the user enters a negative number, math.sqrt() raises ValueError: math domain error, since square roots of negatives are not real numbers.
4 Write a program to simulate the rolling of a dice using the random module.random module

A dice must produce a whole number from 1 to 6, with 6 included. That is exactly what randint(1, 6) does.

Program:

import random

random.seed(21)
for game in range(5):
    dice = random.randint(1, 6)
    print("Roll", game + 1, ":", dice)

Output:

Roll 1 : 2
Roll 2 : 4
Roll 3 : 6
Roll 4 : 4
Roll 5 : 6

Important: the random.seed(21) line is included only so this page can print a fixed result that you can compare against. In your own answer you may leave it out, and then the five numbers will be different on every run - which is the whole point of a dice.

Alternative ways to write the same thing, all giving 1 to 6:

  • random.randrange(1, 7) - the stop value is excluded, so the upper limit must be written as 7.
  • random.randrange(6) + 1 - randrange(6) gives 0 to 5, then add 1.
  • int(random.random() * 6) + 1 - random() gives a float in [0.0, 1.0), so multiplying by 6 gives [0.0, 6.0), and int() truncates it to 0-5.

Common mistake: writing random.randrange(1, 6). That produces only 1, 2, 3, 4, 5 - your dice would never show a six.

5 What is the difference between randint() and randrange()? Explain with an example.random module

Both return a random integer, but they treat the upper limit differently.

random.randint(a, b)random.randrange(start, stop, step)
Upper limitIncludedExcluded
Range produceda <= n <= bstart <= n < stop
Step allowed?NoYes, third argument
Minimum arguments21 - randrange(3) gives 0, 1 or 2
Follows range() rules?NoYes, exactly

Demonstration. First a short sample of 8 values from each, then every distinct value seen over 3000 calls:

import random

random.seed(100)
sample = []
for i in range(8):
    sample.append(random.randint(1, 10))
print("randint(1, 10)  :", sample)

random.seed(100)
sample = []
for i in range(8):
    sample.append(random.randrange(1, 10))
print("randrange(1, 10):", sample)

random.seed(100)
got_int = []
for i in range(3000):
    v = random.randint(1, 10)
    if v not in got_int:
        got_int.append(v)

random.seed(100)
got_rng = []
for i in range(3000):
    v = random.randrange(1, 10)
    if v not in got_rng:
        got_rng.append(v)

print("randint can give  :", sorted(got_int))
print("randrange can give:", sorted(got_rng))

Output:

randint(1, 10)  : [3, 8, 8, 3, 7, 6, 7, 9]
randrange(1, 10): [3, 8, 8, 3, 7, 6, 7, 9]
randint can give  : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
randrange can give: [1, 2, 3, 4, 5, 6, 7, 8, 9]

Notice how misleading a short sample can be - the first two lines look identical. Only when you collect thousands of values does the real difference appear: randint(1, 10) can return 10, and randrange(1, 10) never can.

The step argument is available only to randrange():

import random

random.seed(5)
vals2 = []
for i in range(2000):
    v = random.randrange(5, 30, 5)
    if v not in vals2:
        vals2.append(v)
print("randrange(5, 30, 5) gives:", sorted(vals2))
randrange(5, 30, 5) gives: [5, 10, 15, 20, 25]

Once again 30 is absent, because the stop value is excluded even when a step is given.

6 The marks obtained by seven students are 45, 67, 45, 89, 92, 67 and 45. Write a program using the statistics module to display the mean, median and mode of these marks.statistics module

Program:

import statistics

marks = [45, 67, 45, 89, 92, 67, 45]
print("Marks  :", marks)
print("Mean   :", round(statistics.mean(marks), 2))
print("Median :", statistics.median(marks))
print("Mode   :", statistics.mode(marks))
print("Sorted :", sorted(marks))

Output:

Marks  : [45, 67, 45, 89, 92, 67, 45]
Mean   : 64.29
Median : 67
Mode   : 45
Sorted : [45, 45, 45, 67, 67, 89, 92]

Verification by hand:

  • Mean = (45 + 67 + 45 + 89 + 92 + 67 + 45) / 7 = 450 / 7 = 64.28571428571429. Rounded to two decimal places that is 64.29. Without the round() the program would print the full 64.28571428571429.
  • Median - arrange in order: 45, 45, 45, 67, 67, 89, 92. Seven values, so the median is the 4th, which is 67. Because the count is odd, the answer is an actual data value and prints as an int. You do not need to sort the list before calling median(); it sorts internally. The sorted() line above is only for checking.
  • Mode - 45 occurs three times, 67 twice, 89 and 92 once each. The most frequent value is 45.

Watch the median type. If one student were removed, leaving six marks, the median would become the average of the two middle values and would print as a float such as 67.0. An even-length median is always a float.

Previous-year board questions 4

Q1 What possible output(s) are expected to be displayed on screen at the time of execution of the following program? Also specify the maximum and minimum values that can be assigned to the variables FROM and TO.import randomAR = [20, 30, 40, 50, 60, 70]FROM = random.randint(1, 3)TO = random.randint(2, 4)for K in range(FROM, TO + 1): print(AR[K], end="#") Board pattern - recurring

Step 1 - the ranges. randint() includes both ends.

  • FROM = random.randint(1, 3) so FROM can be 1, 2 or 3. Minimum 1, maximum 3.
  • TO = random.randint(2, 4) so TO can be 2, 3 or 4. Minimum 2, maximum 4.

Step 2 - the loop. range(FROM, TO + 1) runs K from FROM up to TO inclusive (the +1 cancels the usual exclusion). If FROM is greater than TO the range is empty and nothing is printed.

Step 3 - all nine combinations with AR = [20, 30, 40, 50, 60, 70], indexes 0 to 5:

FROMTOOutput
1230#40#
1330#40#50#
1430#40#50#60#
2240#
2340#50#
2440#50#60#
32(nothing printed)
3350#
3450#60#

Answer: any one of 30#40#, 30#40#50#, 30#40#50#60#, 40#, 40#50#, 40#50#60#, 50#, 50#60#, or no output at all when FROM = 3 and TO = 2.

Verification with an actual run (a seed is used so the result here is reproducible):

import random
AR = [20, 30, 40, 50, 60, 70]
random.seed(11)
FROM = random.randint(1, 3)
TO = random.randint(2, 4)
print("FROM =", FROM, " TO =", TO)
for K in range(FROM, TO + 1):
    print(AR[K], end="#")
print()
FROM = 2  TO = 4
40#50#60#

Marking traps: 20 can never be printed, because the smallest index used is 1. 70 can never be printed either, because the largest index used is 4. And most students forget the empty-output case.

Q2 Consider the following code. Which word(s) from the list can never be printed, and what are the maximum and minimum values of P?import randomSIDES = ["EAST", "WEST", "NORTH", "SOUTH"]for I in range(4): P = random.randrange(1, 4) print(SIDES[P], end=":") Board pattern - recurring

Step 1 - the range of P. random.randrange(1, 4) follows range() rules: start included, stop excluded. So P can be 1, 2 or 3. Minimum value of P = 1, maximum value of P = 3.

Step 2 - which words those indexes reach.

IndexSIDES[index]Reachable?
0EASTNo - P is never 0
1WESTYes
2NORTHYes
3SOUTHYes

Answer: EAST can never be printed. The loop runs 4 times, so the output is always four words separated by colons, each one chosen from WEST, NORTH or SOUTH, with repeats allowed.

An actual run (seeded so it is reproducible):

import random
SIDES = ["EAST", "WEST", "NORTH", "SOUTH"]
random.seed(9)
for I in range(4):
    P = random.randrange(1, 4)
    print(SIDES[P], end=":")
print()
NORTH:SOUTH:NORTH:NORTH:

Repeats are normal - each pass draws independently, so the same word can appear several times.

The fix, if all four words were wanted: use random.randrange(0, 4) or random.randint(0, 3). Both give indexes 0 to 3. Note that random.randint(1, 4) would be wrong in a different way - it can return 4, and SIDES[4] raises IndexError: list index out of range.

Q3 Write the output of the following code, giving a reason for the data type of each result.import mathn = 4567.892print(math.floor(n))print(math.ceil(n))print(round(math.sqrt(n), 3))print(math.fabs(-n))print(math.pow(2, 10)) CBSE Class 11 pattern (2 marks)

Output:

4567
4568
67.586
4567.892
1024.0

Line-by-line reasoning:

  1. math.floor(4567.892) moves down to the nearest whole number, giving 4567. floor() returns an int, which is why no .0 is printed.
  2. math.ceil(4567.892) moves up, giving 4568. ceil() also returns an int.
  3. math.sqrt(4567.892) is 67.58618201969985 and round(x, 3) trims it to 67.586. sqrt() returns a float, and round() with a second argument keeps it a float.
  4. math.fabs(-4567.892) strips the sign, giving 4567.892. fabs() always returns a float - this is its difference from the built-in abs(), which would keep an int input as an int.
  5. math.pow(2, 10) gives 1024.0, not 1024, because math.pow() converts both arguments to float and always returns a float. The built-in pow(2, 10) and the operator 2 ** 10 would both give the int 1024.

The rule to memorise: of the math items in your syllabus, everything returns a float except ceil() and floor(), which return int. Dropping a .0 in a written answer costs half a mark.

Q4 The following code is written to display the mean of a list of values and the square root of 16. Rewrite it after correcting the errors, and underline each correction.import Mathvalues = [12, 15, 12, 18, 20]print(mean(values))print(math.Sqrt(16)) CBSE Class 11 pattern (error finding)

Errors, in the order Python meets them:

  1. import Math - module names are case-sensitive. The module is math, all lowercase. This is the first error hit, and the program stops immediately with ModuleNotFoundError: No module named 'Math'.
  2. print(mean(values)) - mean() lives in the statistics module, which was never imported. Even if it had been imported as import statistics, the bare name mean would still fail; it needs either statistics.mean(values) or a from statistics import mean.
  3. math.Sqrt(16) - function names are case-sensitive too. It is sqrt, not Sqrt. This one raises AttributeError: module 'math' has no attribute 'Sqrt'.

Corrected program (corrections shown in bold, which is what underlining means in a written answer):

import math
from statistics import mean

values = [12, 15, 12, 18, 20]
print(mean(values))
print(math.sqrt(16))

Output of the corrected program:

15.4
4.0

Check: (12 + 15 + 12 + 18 + 20) / 5 = 77 / 5 = 15.4. And math.sqrt(16) is 4.0, not 4, because sqrt() always returns a float.

An equally acceptable correction uses the prefix style throughout:

import math
import statistics

values = [12, 15, 12, 18, 20]
print(statistics.mean(values))
print(math.sqrt(16))

Either version earns full marks, as long as the import matches the way the function is called.

Part of Priodemy for School

Interactive CBSE lessons, Class 8–12 — free with every school on Priodemy EduSuite. Explore more chapters and labs on the Priodemy for School hub.

Ask AI