LambdaLabTM
Artificial Intelligence · Class 10 Project · Subject Code 417
Sample ProjectStatistical DataSDG 6 · SDG 11⏱️ 15 min read

Grouping Homes by Water Use

Clustering with no labels at all: k-means finds the careful, typical and heavy water users in a housing society by itself.

1Introduction

Every summer the society's tank runs low, and every summer the same notice goes up: "Please save water." Nobody reads it, because it speaks to everyone and so to no one.

The flats are not all the same. Some already use little. Some water a garden for half an hour a day. Some wash a car every other day. Each needs different advice.

But nobody has sorted the flats into groups, and there is no column saying who is careful and who is not. This project asks the computer to find those groups on its own.

6SDG 6
Clean Water and Sanitation
11SDG 11
Sustainable Cities and Communities
how it connects to the goal

SDG 6 asks for water to be used efficiently, and many Indian cities run short every summer. A housing society that knows which homes use the most water, and why, can give each group advice that fits — and save far more than a single notice on the board ever does.

The curriculum asks that the project be related to the Sustainable Development Goals. That link is the reason the problem is worth solving, so it belongs at the very start of the report, not tacked on at the end.

2Stage 1 — Problem Scoping

Problem scoping means understanding the problem properly before building anything. The tool for it is the 4Ws Problem Canvas: who has the problem, what the problem is, where it happens, and why solving it matters.

Who?the stakeholders
  • The society's water committee
  • Every family in the society, who share one tank
What?the problem
  • Water runs short every summer
  • One general notice does not change anybody's habits
Where?the context
  • A housing society with a water meter in every flat
  • In the summer months
Why?the benefit
  • Advice that fits a household is more likely to be followed
  • Saving water keeps the tank from running dry

The problem statement

The four answers are then joined into one sentence, using the curriculum's problem statement template:

Our the water committee of our housing society have a problem of getting families to save water when the tank runs low every summer and a general notice is ignored. An ideal solution would find groups of similar households, so each group can be given advice that fits it.
who would use it

The water committee of a housing society with a meter in every flat, or a municipal water department planning a saving campaign.

How it is done today

A notice on the board

Cheap and quick, and almost always ignored, because it is written for everyone at once.

The monthly water bill

Some societies split the bill by meter. It tells a family how much they used, but not whether that is a lot, or what to change.

Fixed rules

For example, "no garden watering after 8 am". Useful, but the committee has to guess which habits matter most.

Objectives

  1. To read a month of water-meter data for 300 flats from a CSV file
  2. To remove blank and impossible (negative) meter readings
  3. To compare the society's use with the city norm of 135 litres a person a day
  4. To find groups of similar flats with k-means clustering, without any labels
  5. To check how many groups is the right number, and whether the groups are stable
  6. To place new flats into a group and suggest a tip for each

📘 Revise: Stage 1 — Problem Scoping

3Stage 2 — Data Acquisition

Each row is one flat: the litres used per person per day over the month (from the meter, divided by the number of people), the minutes a day the garden is watered, and the car washes in a week. There is no label — no column says which group a flat belongs to.

The file has 300 rows.

For a real project, ask the society office for a month of meter readings and send a short form to every flat asking about gardens and cars. Use flat numbers only, and get the committee's permission first.

Watch Out
This dataset is a LambdaLab sample. A program made it to behave like the real thing, so the project can be run before you have collected anything. It is not real data. In your own report, use data you collected or a public dataset, and say where it came from.

Features and label

The features are what the model is given. The label is the answer it has to learn to give.

ColumnTypeWhat it holdsRole
litres_per_personintegerLitres used per person per day, from the meterfeature
garden_minutesintegerMinutes a day the garden or plants are wateredfeature
car_washes_weekintegerCar or scooter washes in a weekfeature
flat_notextFlat number, never a namenot used

The first eight rows of water_use.csv

flat_nolitres_per_persongarden_minutescar_washes_week
F1019192
F102196394
F103234364
F104146101
F10513692
F10613191
F10710340
F1089140

Inside water_use.csv

Three hundred flats and one month of water use. There is no label column. Some meter readings are blank and two are negative. The whole file is 300 rows, 3.9 KB. It comes with the project download, and you can take the file on its own.

📘 Revise: Stage 2 — Data Acquisition

4Stage 3 — Data Exploration

Data exploration means getting to know the data before trusting it: fixing what is wrong with it, then drawing it to see the patterns. The charts here are what decide what the model should be.

What was wrong with the data

Blank meter readings
3 (removed)

A flat with no reading cannot be grouped, so it is removed and counted.

Negative litres
2 (removed)

Water used cannot be less than zero. A negative number means the meter was reset in the middle of the month, so the reading is wrong and is removed. A reading of exactly 0 is kept — the flat may have been empty.

Features on different scales
litres in hundreds, car washes under 5

Each feature is rescaled to run from 0 to 1, so litres do not decide the groups on their own.

1Water used by one person in a day
Histogram
Water used by one person in a day
how to read it

Each bar counts the flats in a range of litres per person per day. The red dashed line is 135 litres, the amount city planners use for one person in a day.

what it shows

145 of 295 flats are above the norm. The histogram is not one smooth hill — there is a separate bump on the right, around 200 litres. A bump like that is a sign that the data holds more than one kind of household.

drawn by the program · saved as chart1_litres.png
2Every flat, before grouping
Scatter plot
Every flat, before grouping
how to read it

Each dot is one flat: litres per person across, garden minutes up. All the dots are grey because there are no labels.

what it shows

Even in grey, the dots are not spread evenly. There is a crowd at the bottom left, another in the middle, and a looser group at the top right. Our eyes see groups — the question is whether the computer finds the same ones.

drawn by the program · saved as chart2_before.png
3The three groups k-means found
Scatter plot
The three groups k-means found
how to read it

The same scatter plot, now coloured by the group each flat was put in.

what it shows

The computer found the three crowds. Careful flats use about 96 litres a person and hardly water a garden. Typical flats use about 139. Heavy flats use about 217 litres and water a garden for over half an hour a day — the garden is a large part of why they are heavy.

The names Careful, Typical and Heavy were given by the program after the groups were found, by sorting them by average litres. K-means itself only says group 0, 1 and 2. Giving the groups meaning is a person's job.

drawn by the program · saved as chart3_groups.png

📘 Revise: Stage 3 — Data Exploration

5Stage 4 — Modelling

Where this model sits in the family of AI models:

There is no label: no column says who is careful and who is heavy. When a model has to find patterns in data without being told the answers, it is unsupervised learning. Finding groups of similar items is clustering.

How the model works

The model is k-means, with k = 3 groups. It works in four steps. One: pick three flats at random as the first group centres. Two: put every flat in the group whose centre is nearest. Three: move each centre to the middle (the average) of the flats in its group. Four: repeat steps two and three until the groups stop changing.

K-means has one known weakness, and the program shows it. If two of the random first centres happen to land in the same crowd, it can get stuck with a poor grouping. In this run, one of the five starts got stuck. So the program runs k-means from 5 different random starts and keeps the grouping with the smallest spread — 4 of 5 starts found that same best grouping. Orange's k-Means widget does exactly this, and calls it re-runs.

📘 Revise: Stage 4 — Modelling

6Stage 5 — Evaluation

An unsupervised model cannot have an accuracy, because there are no right answers to compare with. It has to be judged in other ways, and this project uses two.

First, is 3 the right number of groups? The elbow chart answers that: the spread falls a lot up to k = 3 and only a little after it.

Second, are the groups real, or did the computer invent them? If they are real, k-means should find the same groups from different random starts. Four of the five starts ended with exactly the same grouping, and the fifth got stuck in a clearly worse one (a larger spread). That is strong evidence the three groups are really in the data.

Finally, a person has to check that the groups make sense. Here they do: each group has a clear story — careful users, typical users, and users with a garden and a car to wash.

4Choosing k: how tight the groups are
Line chart
Choosing k: how tight the groups are
how to read it

Across is k, the number of groups. Up is the spread: the total distance from every flat to the centre of its group. Lower means tighter groups.

what it shows

Going from 2 groups to 3 cuts the spread by 5.94. Going from 3 to 4 cuts it by only 1.82. The line bends — like an elbow — at k = 3. After that, extra groups only split real groups into smaller pieces. So 3 is the right number.

drawn by the program · saved as chart4_elbow.png
5Flats in each group
Bar chart
Flats in each group
how to read it

One bar per group: how many flats are in it.

what it shows

The heavy group is the smallest, at about a quarter of the society. That is useful: the committee can talk to 70 households about gardens and cars instead of 295 about everything.

drawn by the program · saved as chart5_group_sizes.png

📘 Revise: The Confusion Matrix

7Stage 6 — Deployment

When a new family moves in, its first month of readings is placed in the group whose centre is nearest, and the program prints the tip for that group: a thank-you for careful users, help with taps for typical users, and garden and car advice for heavy users.

In real use the committee would run this every month and send each group its own message. Next summer, running it again would show whether flats have moved from the heavy group to the typical one.

📘 Revise: Stage 6 — Deployment

8Ethical concerns

An AI project is not finished when the model works. The curriculum asks three questions of every model — is it fair (bias), can it explain itself (transparency), and who answers for its mistakes (accountability). For this project:

Labels can hurt

"Heavy" is a description of water use, not a judgement of a family. A large family or someone who is unwell may need more water. Messages to each group should be helpful and private, never a list on the notice board.

Privacy

Water use shows when a family is at home. Readings should be stored with flat numbers, seen only by the committee, and used only for saving water.

Transparency

The groups are explained by their averages, which anyone can read. A family can see why it was put in a group.

📘 Revise: Bias, Transparency, Accountability

9Source code

The whole program — it is also in the downloads at the top of this page — in Python with pandas, NumPy and matplotlib — the three libraries of Unit 7. It is laid out as the six stages of the AI Project Cycle, one after another. Every chart on this page was drawn by it, and every number came out of running it.

water_groups.py
# ---------------------------------------------------------------------------
# water_groups.py
#
# AI project on Statistical Data: finding groups of households by how they
# use water, so that a housing society can give each group the right advice.
#
# CBSE Class X, Artificial Intelligence (417), Part C project work.
# SDG 6 (Clean Water and Sanitation) and SDG 11 (Sustainable Cities).
#
# This is UNSUPERVISED learning. Nobody has told the program which flats are
# careful and which are wasteful - there is no label column at all. The
# model, k-means clustering, finds the groups by itself:
#   1. pick k flats at random as the first group centres
#   2. put every flat in the group whose centre is nearest
#   3. move each centre to the middle (the mean) of its group
#   4. repeat steps 2 and 3 until nothing changes
#
# Libraries: pandas (the data), numpy (the arithmetic), matplotlib (charts).
# ---------------------------------------------------------------------------

# %% Stage 1 - Problem scoping
print("=" * 60)
print("GROUPING HOMES BY WATER USE")
print("=" * 60)
print("Who   : the housing society's water committee")
print("What  : one notice for everyone is ignored by everyone")
print("Where : a society with a water meter in every flat")
print("Why   : advice that fits each group saves more water")
print()

# %% Stage 2 - Data acquisition
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

pd.set_option("display.width", 120)
pd.set_option("display.max_columns", 20)

data = pd.read_csv("water_use.csv")

print("--- Stage 2: Data acquisition ---")
print("Rows read    :", len(data))
print("Columns read :", len(data.columns))
print()
print("The first 10 rows:")
print(data.head(10))
print()

# %% Stage 3 - Data exploration
print("--- Stage 3: Data exploration ---")
features = ["litres_per_person", "garden_minutes", "car_washes_week"]

missing = data["litres_per_person"].isnull().sum()
data = data.dropna(subset=["litres_per_person"])
print("Rows with no meter reading     :", missing, "(removed)")

# Water used cannot be negative; a negative number means the meter was reset.
negative = (data["litres_per_person"] < 0).sum()
data = data[data["litres_per_person"] >= 0]
print("Rows with negative litres      :", negative, "(removed)")
print("Rows left                      :", len(data))
print()

litres = data["litres_per_person"]
print("Litres per person a day - mean   :", round(np.mean(litres), 1))
print("Litres per person a day - median :", np.median(litres))
over = (litres > 135).sum()
print("Flats above 135 litres a person  :", over, "of", len(data))
print()

# Chart 1: how much water a person uses in a day, across the society.
plt.figure(figsize=(7, 4))
plt.hist(litres, bins=25, color="#4e9fd1")
plt.axvline(135, color="#e15759", linestyle="--", label="135 litres: the city norm")
plt.title("Water used by one person in a day")
plt.xlabel("Litres per person per day")
plt.ylabel("Number of flats")
plt.legend()
plt.tight_layout()
plt.savefig("chart1_litres.png")
plt.close()

# Chart 2: litres against garden watering, before any grouping.
plt.figure(figsize=(7, 5))
plt.scatter(data["litres_per_person"], data["garden_minutes"], color="#999999", alpha=0.7, s=18)
plt.title("Every flat, before grouping")
plt.xlabel("Litres per person per day")
plt.ylabel("Garden watering (minutes a day)")
plt.grid(True, linestyle="--", alpha=0.4)
plt.tight_layout()
plt.savefig("chart2_before.png")
plt.close()

# %% Stage 4 - Modelling
print("--- Stage 4: Modelling ---")

# Rescale every feature to run from 0 to 1, so litres (in hundreds) do not
# outweigh car washes (under 5).
low = data[features].min()
high = data[features].max()
scaled = (data[features] - low) / (high - low)

k = 3
starts = 5

# k-means can get stuck if two of its random first centres land in the same
# group. So it is run from 5 different random starts, and the grouping with
# the smallest SPREAD (the total distance from every flat to its own centre)
# is kept. Orange's k-Means widget does the same thing ("re-runs").
best_spread = None
same_as_best = 0
for start in range(starts):
    # Step 1: k flats chosen at random are the first centres.
    centres_try = scaled.sample(k, random_state=start).reset_index(drop=True)
    for step in range(20):
        # Step 2: distance from every flat to each centre; join the nearest.
        distance = pd.DataFrame()
        for c in range(k):
            distance[c] = ((scaled - centres_try.iloc[c]) ** 2).sum(axis=1)
        group_try = distance.idxmin(axis=1)
        # Step 3: move each centre to the mean of the flats in its group.
        centres_try = scaled.groupby(group_try).mean().reset_index(drop=True)
    spread = round(distance.min(axis=1).sum(), 4)
    print("Start", start + 1, "- spread :", spread)
    if best_spread is None or spread < best_spread:
        best_spread = spread
        group = group_try
        centres = centres_try
        same_as_best = 1
    elif spread == best_spread:
        same_as_best = same_as_best + 1
print("Starts that found the best grouping :", same_as_best, "of", starts)
print()

# Name the groups by how much water they use, smallest first.
order = data.groupby(group)["litres_per_person"].mean().sort_values().index
names = ["Careful", "Typical", "Heavy"]
data["group"] = group.replace({order[0]: names[0], order[1]: names[1], order[2]: names[2]})
print()

summary = data.groupby("group")[features].mean().round(1).reindex(names)
summary["flats"] = data["group"].value_counts().reindex(names)
print("What each group looks like (averages):")
print(summary)
print()

colours = ["#59a14f", "#4e9fd1", "#e15759"]

# Chart 3: the same scatter, now coloured by the group k-means found.
plt.figure(figsize=(7, 5))
for i in range(k):
    rows = data[data["group"] == names[i]]
    plt.scatter(rows["litres_per_person"], rows["garden_minutes"],
                color=colours[i], alpha=0.75, s=18, label=names[i])
plt.title("The three groups k-means found")
plt.xlabel("Litres per person per day")
plt.ylabel("Garden watering (minutes a day)")
plt.legend()
plt.grid(True, linestyle="--", alpha=0.4)
plt.tight_layout()
plt.savefig("chart3_groups.png")
plt.close()

# %% Stage 5 - Evaluation
print("--- Stage 5: Evaluation ---")

# There is no label, so there is no accuracy. Two other checks are used.

# There is no label, so there is no accuracy. Instead the program checks
# how tight the groups are for different values of k. Spread falls as k
# grows, because more centres means every flat has one closer by. The k
# after which it stops falling quickly is a good choice - the "elbow".
spreads = []
for kk in range(1, 6):
    best_kk = None
    for start in range(starts):
        c_kk = scaled.sample(kk, random_state=start).reset_index(drop=True)
        for step in range(20):
            d = pd.DataFrame()
            for c in range(kk):
                d[c] = ((scaled - c_kk.iloc[c]) ** 2).sum(axis=1)
            g = d.idxmin(axis=1)
            c_kk = scaled.groupby(g).mean().reset_index(drop=True)
        s_kk = d.min(axis=1).sum()
        if best_kk is None or s_kk < best_kk:
            best_kk = s_kk
    spreads.append(best_kk)
for kk in range(1, 6):
    print("Spread with k =", kk, ":", round(spreads[kk - 1], 2))
print()
print("Drop in spread from k = 2 to k = 3 :", round(spreads[1] - spreads[2], 2))
print("Drop in spread from k = 3 to k = 4 :", round(spreads[2] - spreads[3], 2))
print()

# Chart 4: the elbow chart.
plt.figure(figsize=(7, 4))
plt.plot(range(1, 6), spreads, marker="o", color="#4e9fd1")
plt.title("Choosing k: how tight the groups are")
plt.xlabel("k (number of groups)")
plt.xticks(range(1, 6))
plt.ylabel("Spread (total distance to centres)")
plt.grid(True, linestyle="--", alpha=0.4)
plt.tight_layout()
plt.savefig("chart4_elbow.png")
plt.close()

# Chart 5: how many flats in each group.
plt.figure(figsize=(6, 4))
plt.bar(names, summary["flats"], color=colours)
plt.title("Flats in each group")
plt.ylabel("Number of flats")
plt.tight_layout()
plt.savefig("chart5_group_sizes.png")
plt.close()

# %% Stage 6 - Deployment
print("--- Stage 6: Deployment ---")
tips = {
    "Careful": "Thank you note - keep it up",
    "Typical": "Fix dripping taps; use a bucket, not a shower",
    "Heavy":   "Water the garden at dawn; wash the car with a bucket",
}
new_flats = pd.DataFrame({
    "litres_per_person": [95, 140, 230],
    "garden_minutes":    [0, 12, 35],
    "car_washes_week":   [0, 1, 4],
})
new_scaled = (new_flats - low) / (high - low)
# Give each centre its group's name, so the nearest centre IS the answer.
centres = centres.rename(index={order[0]: names[0], order[1]: names[1], order[2]: names[2]})
for i in range(len(new_flats)):
    d = ((centres - new_scaled.iloc[i]) ** 2).sum(axis=1)
    g = d.idxmin()
    f = new_flats.iloc[i]
    print(int(f["litres_per_person"]), "litres,", int(f["garden_minutes"]), "garden min,",
          int(f["car_washes_week"]), "car washes ->", g, "->", tips[g])

The library calls it is built from

CallWhereWhat it does
pd.read_csv()Stage 2Reads the CSV file
data[data["litres_per_person"] >= 0]Stage 3Removes impossible negative readings
plt.hist / plt.axvlineStage 3The histogram and the 135-litre line
scaled.sample(k, random_state=...)Stage 4Picks k random flats as the first centres
distance.idxmin(axis=1)Stage 4Puts each flat in the group of its nearest centre
scaled.groupby(group).mean()Stage 4Moves each centre to the middle of its group
distance.min(axis=1).sum()Stages 4, 5The spread: total distance from each flat to its centre
replace({...})Stage 4Gives the groups names after they are found
plt.scatter / plt.plot / plt.barStages 3, 5Scatter plots, the elbow chart and the group sizes

10Sample output

A real run, exactly as it appeared. Nothing below was typed by hand.

Output
============================================================
GROUPING HOMES BY WATER USE
============================================================
Who   : the housing society's water committee
What  : one notice for everyone is ignored by everyone
Where : a society with a water meter in every flat
Why   : advice that fits each group saves more water

--- Stage 2: Data acquisition ---
Rows read    : 300
Columns read : 4

The first 10 rows:
  flat_no  litres_per_person  garden_minutes  car_washes_week
0    F101               91.0               9                2
1    F102              196.0              39                4
2    F103              234.0              36                4
3    F104              146.0              10                1
4    F105              136.0               9                2
5    F106              131.0               9                1
6    F107              103.0               4                0
7    F108               91.0               4                0
8    F109               90.0               4                0
9    F110              210.0              35                4

--- Stage 3: Data exploration ---
Rows with no meter reading     : 3 (removed)
Rows with negative litres      : 2 (removed)
Rows left                      : 295

Litres per person a day - mean   : 142.6
Litres per person a day - median : 133.0
Flats above 135 litres a person  : 145 of 295

--- Stage 4: Modelling ---
Start 1 - spread : 9.8589
Start 2 - spread : 9.8589
Start 3 - spread : 13.9302
Start 4 - spread : 9.8589
Start 5 - spread : 9.8589
Starts that found the best grouping : 4 of 5


What each group looks like (averages):
         litres_per_person  garden_minutes  car_washes_week  flats
group                                                             
Careful               95.7             4.3              0.3    102
Typical              138.9            11.9              1.4    123
Heavy                217.4            33.4              3.2     70

--- Stage 5: Evaluation ---
Spread with k = 1 : 50.63
Spread with k = 2 : 15.8
Spread with k = 3 : 9.86
Spread with k = 4 : 8.04
Spread with k = 5 : 6.48

Drop in spread from k = 2 to k = 3 : 5.94
Drop in spread from k = 3 to k = 4 : 1.82

--- Stage 6: Deployment ---
95 litres, 0 garden min, 0 car washes -> Careful -> Thank you note - keep it up
140 litres, 12 garden min, 1 car washes -> Typical -> Fix dripping taps; use a bucket, not a shower
230 litres, 35 garden min, 4 car washes -> Heavy -> Water the garden at dawn; wash the car with a bucket

11The same project without code

Orange's k-Means widget does the same clustering, including the re-runs from several random starts, and its Silhouette Plot is another way to check the groups. The tool is Orange Data Mining.

1
File

Load water_use.csv. Set flat_no as a meta attribute. Leave the target empty — there is no label.

2
Select Rows

Keep rows where litres_per_person is at least 0.

3
Preprocess

Normalise the features to the 0–1 range.

4
k-Means

Try 2 to 5 clusters. Orange scores each k; choose 3. Its re-runs setting is the same idea as this program's 5 starts.

5
Scatter Plot

Plot litres_per_person against garden_minutes and colour by Cluster — the same as chart 3.

6
Box Plot

Split litres_per_person by Cluster to describe each group.

Tip
Doing both is the strongest report. The no-code version shows you can use the curriculum's tool; the Python version shows you understand what the tool is doing underneath. If the numbers come out slightly different, that is expected — the tool picks its own training rows and its own model settings — and saying why in the report is worth marks.

📘 Revise: No-Code AI, and Orange

12Testing

Every case below was actually run, and the result is what came back. Each one gives the whole program a small input of its own.

Test caseExpectedActualResult
The full datasetRows read : 300Rows read : 300Pass
Blank meter readings are removedRows with no meter reading : 3 (removed)Rows with no meter reading : 3 (removed)Pass
Negative litres (a meter reset) are removedRows with negative litres : 2 (removed)Rows with negative litres : 2 (removed)Pass
One of the five random starts gets stuck; the other four agreeStarts that found the best grouping : 4 of 5Starts that found the best grouping : 4 of 5Pass
The elbow: going from 2 to 3 groups helps far more than 3 to 4Drop in spread from k = 3 to k = 4 : 1.82Drop in spread from k = 3 to k = 4 : 1.82Pass
Three obvious groups of 15: the careful group is found95 litres, 0 garden min, 0 car washes -> Careful -> Thank you note - keep it up95 litres, 0 garden min, 0 car washes -> Careful -> Thank you note - keep it upPass
...and the heavy group is found230 litres, 35 garden min, 4 car washes -> Heavy -> Water the garden at dawn; wash the car with a bucket230 litres, 35 garden min, 4 car washes -> Heavy -> Water the garden at dawn; wash the car with a bucketPass
Boundary: 0 litres is kept (an empty flat), -1 is removedRows with negative litres : 1 (removed)Rows with negative litres : 1 (removed)Pass
The data file is missingFileNotFoundError: [Errno 2] No such file or directory: 'water_use.csv'FileNotFoundError: [Errno 2] No such file or directory: 'water_use.csv'Pass

13Results, limits and what next

what the project found
  • 145 of 295 flats use more than the city norm of 135 litres a person a day.
  • With no labels at all, k-means found three clear groups: careful (about 96 litres), typical (about 139) and heavy (about 217).
  • Heavy users water their gardens for over half an hour a day — the clearest single habit to change.
  • The elbow chart shows three is the right number of groups.
  • One random start in five got stuck in a worse grouping; running from several starts solves it.

What this model cannot do

  • One month only; a family on holiday looks careful
  • Litres per person depends on the family size being reported correctly
  • Only three features; bathing habits, washing machines and leaks are not included
  • K-means finds round groups; a group with an odd shape would be split wrongly

What to add next

This is where you make the project your own. Take one of these, or an idea of yours:

  • Add a fourth feature, such as whether the flat has a washing machine
  • Collect three months and see which flats move between groups
  • Send each group its tip and measure the change the next month
  • Try k-means on your own school's water or electricity use

What you may have to learn on your own

  • The difference between supervised and unsupervised learning
  • How k-means works, step by step, and why it is run from several starts
  • How to choose k with the elbow method
  • Why an unsupervised model cannot have an accuracy, and how it is judged instead

14Conclusion

The project found three groups of households from meter readings alone, with nobody telling the computer what the groups should be. The groups are clear and they make sense: careful users, typical users, and heavy users who water gardens and wash cars.

Judging an unsupervised model needed different tools. There was no accuracy to report, so the project checked the number of groups with the elbow chart and checked that the groups were real by finding them again from different random starts.

The testing also found a real weakness of k-means: a single random start can get stuck. Running it from five starts fixed that, and seeing it go wrong was more useful than if it had always worked.

15Questions for the viva

The project carries a viva voce of its own, worth 5 marks. These are the questions it is most likely to ask about this project. Try answering each one aloud before opening it.

▸Is your project supervised or unsupervised? Why?

Unsupervised, because there is no label. Nobody told the model which flats are careful or heavy; it found the groups itself.

▸Explain how k-means works.

Pick k random points as centres. Put every flat in the group of its nearest centre. Move each centre to the average of its group. Repeat until the groups stop changing.

▸How did you choose k = 3?

With the elbow chart. The spread fell a lot from 2 to 3 groups and only a little after that.

▸Why did you run k-means five times?

Because a bad random start can make it get stuck. Running five times and keeping the tightest grouping avoids that. One of my five starts did get stuck.

▸Why is there no accuracy in your evaluation?

Accuracy needs the right answers to compare with, and unsupervised data has no labels.

▸Who named the groups Careful, Typical and Heavy?

The program named them after they were found, by sorting by average litres. K-means itself only numbers the groups.

▸Which SDG does it relate to?

SDG 6, Clean Water and Sanitation, and SDG 11, Sustainable Cities and Communities.

16References

  • CPHEEO, Manual on Water Supply and Treatment, Ministry of Housing and Urban Affairs — the 135 litres per person per day norm for cities with sewerage.
  • Jal Shakti Abhiyan, Ministry of Jal Shakti — https://jsactr.mowr.gov.in/
  • Orange Data Mining, k-Means widget — https://orangedatamining.com/widget-catalog/unsupervised/kmeans/
  • CBSE, Artificial Intelligence (Subject Code 417), Class X curriculum, 2026-27 — Part C, Practical and Project Work
  • CBSE, Artificial Intelligence Facilitator Handbook, Class X
  • pandas — https://pandas.pydata.org/docs/; NumPy — https://numpy.org/doc/; Matplotlib — https://matplotlib.org/stable/
  • LambdaLab — https://www.lambdalab.in
Key Takeaway
The PDF is the whole report — cover page, certificate, acknowledgement, index and every section on this page, ready to print. The cover, certificate and acknowledgement have blank lines where the names go: a certificate with somebody else's name printed on it is not a template. Fill those in, and build the project on data you collected yourself.