Students write a nearest-neighbour model from scratch, measuring the distance between penguins with Pythagoras and copying the species of the closest one, then ask for measurements, add a "not sure" answer and guess five mystery penguins.
Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.
Predict and Run 10 min · Investigate 10 min · Modify 15 min · Make 10 min
Read the unit primer first. Nearest neighbour keeps every example and answers with the species of the closest one; the distance is Pythagoras on bill and flipper length (** 2 squares, ** 0.5 is the square root). nearest() is Data detective's "smallest so far" pattern (the bug task uses >, finding the furthest penguin). A big distance means nothing similar is known, so the guess is weak, but the model still answers: the "Not sure" task and the optional last lesson come back to this. The info question raises scale: flippers (around 200) outweigh bills (around 45) in the distance.
The smallest-so-far loop inside nearest(). Ask students to trace it for three penguins on paper before they fix the bug.
| What you will see | A question to ask | The fix |
|---|---|---|
| The guess is always the same species. | Does nearest() compare with < and update best? | if distance(p, new) < distance(best, new): best = p. |
| TypeError: unsupported operand type(s) for -: 'str' and 'float'. | Was the typed answer turned into a number? | float(input(...)). |
| Distance 0.0 for a mystery penguin. | Is that penguin's exact measurement in the data? | Nothing is wrong; it matched a real penguin exactly. |
One possible solution for each task. Other correct programs are fine.
import csv
penguins = []
with open("penguins.csv") as f:
for row in csv.DictReader(f):
penguins.append({"species": row["species"], "bill": float(row["bill_length_mm"]), "flipper": float(row["flipper_length_mm"])})
def distance(a, b):
return ((a["bill"] - b["bill"]) ** 2 + (a["flipper"] - b["flipper"]) ** 2) ** 0.5
def nearest(new):
best = penguins[0]
for p in penguins:
if distance(p, new) < distance(best, new):
best = p
return best
bill = float(input("Bill length (mm)? "))
flipper = float(input("Flipper length (mm)? "))
mystery = {"bill": bill, "flipper": flipper}
match = nearest(mystery)
print("Nearest penguin: " + match["species"] + ", bill " + str(match["bill"]) + ", flipper " + str(match["flipper"]))
print("Distance: " + str(round(distance(match, mystery), 1)))
print("Guess: " + match["species"])
import csv
penguins = []
with open("penguins.csv") as f:
for row in csv.DictReader(f):
penguins.append({"species": row["species"], "bill": float(row["bill_length_mm"]), "flipper": float(row["flipper_length_mm"])})
def distance(a, b):
return ((a["bill"] - b["bill"]) ** 2 + (a["flipper"] - b["flipper"]) ** 2) ** 0.5
def nearest(new):
best = penguins[0]
for p in penguins:
if distance(p, new) < distance(best, new):
best = p
return best
mystery = {"bill": 47.0, "flipper": 215.0}
match = nearest(mystery)
print("Nearest penguin: " + match["species"] + ", bill " + str(match["bill"]) + ", flipper " + str(match["flipper"]))
print("Distance: " + str(round(distance(match, mystery), 1)))
if distance(match, mystery) > 10:
print("Not sure: no penguin in the data is close")
else:
print("Guess: " + match["species"])
import csv
penguins = []
with open("penguins.csv") as f:
for row in csv.DictReader(f):
penguins.append({"species": row["species"], "bill": float(row["bill_length_mm"]), "flipper": float(row["flipper_length_mm"])})
def distance(a, b):
return ((a["bill"] - b["bill"]) ** 2 + (a["flipper"] - b["flipper"]) ** 2) ** 0.5
def nearest(new):
best = penguins[0]
for p in penguins:
if distance(p, new) < distance(best, new):
best = p
return best
mysteries = [
{"bill": 38.0, "flipper": 185.0},
{"bill": 49.5, "flipper": 196.0},
{"bill": 46.0, "flipper": 218.0},
{"bill": 44.0, "flipper": 201.0},
{"bill": 60.0, "flipper": 240.0},
]
for m in mysteries:
match = nearest(m)
print(str(m["bill"]) + ", " + str(m["flipper"]) + ": " + match["species"] + " (distance " + str(round(distance(match, m), 1)) + ")")
Stretch: Make the three nearest penguins vote (sort by distance with sorted(penguins, key=lambda p: distance(p, mystery))[:3]).
Support: Give students the Not sure if, and ask them only to choose the number.
How does a nearest-neighbour model guess a new penguin's species?
It finds the penguin in its data with the closest measurements and gives that penguin's species.
Next lesson tests the model fairly, on penguins it has not seen. scikit-learn's KNeighborsClassifier (Lesson 4) is this model.