Week 12: AI and Data Basics — Jupyter, NumPy, pandas, matplotlib
Month 3 | Main goal: The student understands what AI/ML is in simple language, works inside a Jupyter notebook in VS Code, and uses NumPy, pandas and matplotlib to read, clean, analyse and plot real data.
Teacher Preparation (read before the week starts)
Good news: this week is easier than Week 11 — no server and no auth. But it is a new world (notebooks, data), so run every cell yourself first.
- Read:
- VS Code — "Jupyter Notebooks in VS Code": https://code.visualstudio.com/docs/datascience/jupyter-notebooks
- NumPy — "NumPy: the absolute basics for beginners": https://numpy.org/doc/stable/user/absolute_beginners.html (the first sections up to "Basic array operations")
- pandas — "10 minutes to pandas": https://pandas.pydata.org/docs/user_guide/10min.html
- matplotlib — "Quick start guide": https://matplotlib.org/stable/users/explain/quick_start.html
- Optional (understanding AI in simple language): Google "Machine Learning Crash Course — Introduction" https://developers.google.com/machine-learning/intro-to-ml
-
Install:
- VS Code extension: Jupyter (publisher: Microsoft). It comes with "Jupyter Keymap", "Jupyter Renderers" and so on.
- In this week's new venv:
cd $HOME\Documents mkdir data_wiki12 cd data_wiki12 python -m venv .venv .\.venv\Scripts\Activate.ps1 pip install ipykernel numpy pandas matplotlib
-
About versions: pandas may be 2.x or 3.x depending on when you install it. All of this week's code works on both. The only differences you will see in the output: pandas 3 shows the text dtype as
str(pandas 2:object), andtype(df)aspandas.DataFrame(pandas 2:pandas.core.frame.DataFrame). Don't be alarmed. - Try first: create a notebook, choose the
.venvkernel, run the cellimport pandas as pd; pd.__version__. If you see a version, you are ready. - Terms to understand: AI, Machine Learning, model, training, data, feature, label, prediction, notebook, cell, kernel, array, shape, vectorized, DataFrame, Series, index, NaN, groupby, plot.
Week Goals (by the end of the week the student will be able to...)
- Explain in simple words AI, Machine Learning, model, training and prediction, and why data is the foundation.
- Create and use a Jupyter notebook inside VS Code (cells, kernel, Markdown, restart).
- Create NumPy arrays, read
shape/dtype, do vectorized operations, boolean masks andaxis. - Read a CSV with pandas and explore the data (
head,info,describe,value_counts). - Select columns/rows (
[],loc,iloc), filter by conditions, add columns, sort (sort_values). - Clean data:
isna,fillna,dropna,drop_duplicates,astype,to_datetime. - Use
groupby+sum/mean/count+agg. - Draw line, bar, histogram and scatter plots with matplotlib, and save images.
- See "small ML" in practice: fitting a line (
np.polyfit) and predicting.
Session 1 — AI/ML in Simple Language, Jupyter, and NumPy
Session flow
- Homework review (Todo API: priority, overdue, delete me)
- What is AI? What is ML? (a conversation + everyday examples)
- Jupyter in VS Code: creating a notebook, kernel, cells, Markdown
- NumPy: why? array versus list;
shape,dtype - Vectorized operations, statistics (
sum,mean...), boolean masks - 2D arrays,
axis,reshape - In-class exercise
- Q&A + homework
Explanation for the teacher (read this first)
AI (Artificial Intelligence / Akili Bandia) — a broad term: computers doing work that normally needs human intelligence (recognising speech, translating language, recommending videos, answering questions like ChatGPT/Claude).
Machine Learning (ML / Ujifunzaji wa Mashine) — a part of AI. Instead of a programmer writing all the rules with if/else, we give the computer many examples (data) and it learns the rules (patterns) by itself.
- A normal program: rules + data → answer. (We write the rules.)
- Machine Learning: data + answers → rules (a model). Then the model is used on new data → answer (prediction).
Key terms:
- Data: the examples (for example: 1000 houses and their prices).
- Features: the characteristics used to predict (house size, location, number of rooms).
- Label / target: what we want to predict (the price).
- Training: the process of a model learning from data.
- Model: the thing that has learned — inside, it is calculations (many numbers) that turn features into a prediction.
- Prediction: the model's answer for new data.
- Main types (just mention them): supervised (we have the answers — for example predicting prices), unsupervised (we don't have answers — for example dividing customers into groups), and deep learning / LLMs (very large models such as those running chatbots).
Why do we start with NumPy/pandas? The bulk of ML work (about 80%) is preparing data: reading it, cleaning it, understanding it, and plotting it. The models themselves (scikit-learn, PyTorch) rely on NumPy arrays and pandas DataFrames. Without these, you can't begin.
Jupyter notebook (.ipynb) — a file made up of cells. Each cell is code or Markdown (text). You run one cell at a time (Shift+Enter) and the output appears below it. The kernel is the Python running behind the scenes — it remembers all the variables from cells that have been run. That is why the order of running matters: if you run cell 5 before cell 2, you may get an error. A kernel restart wipes all that memory. If a cell ends with an expression (no print), the notebook shows its value by itself.
NumPy — a library of number arrays, very fast (written in C inside).
np.array([1, 2, 3])— an array; all items are of one type (dtype), unlike a list.- Vectorized:
a * 2multiplies every element — noforloop. With a list,[1,2,3] * 2repeats the list! shape— the dimensions:(5,)is 1D with 5 items;(2, 3)is 2D, 2 rows, 3 columns.- Boolean mask:
a > 1000returns an array ofTrue/False;a[a > 1000]selects those that areTrue. axis: in 2D,axis=0= go down (per column),axis=1= go right (per row).- NumPy 2 in a notebook: if you write
a.sum()as the last line of a cell, you will seenp.int64(8800)— that is just an ordinary number showing its type.print(a.sum())shows8800.
Say this to the student
"Imagine teaching a child to recognise a mango. You don't give the child rules like 'if it is green-yellow, egg-shaped, and has a certain smell...'. You show them many mangoes and say 'this is a mango', and oranges and say 'this is not a mango'. Later they recognise a mango they have never seen. That is Machine Learning: many examples → it learns → it predicts on something new."
"You use ML every day: M-Pesa spotting a suspicious transaction, YouTube recommending videos, Google Translate translating Swahili, your phone unlocking with your face."
"A model is like a cook who has learned by cooking a thousand times — now they know how much salt to add without measuring. Data is their practice. Bad data = a bad cook. That is why today and all this week we are learning to handle data well."
"A notebook is like a scientist's lab book: you write down the experiment (a cell), you see the results right away, and you write notes alongside (Markdown)."
"A Python list is like a market bag — you can put anything in it: tomatoes, a phone, a book. A NumPy array is like an egg tray — each space is only for an egg, but you can carry the whole tray with one hand and do one thing to all of it at once."
Type this code live (share screen)
Step 1 — The first notebook.
- VS Code → open the folder
data_wiki12. Ctrl+Shift+P→ type "Create: New Jupyter Notebook" → Enter.- Top right, click "Select Kernel" → "Python Environments" → choose
.venv. - Save as
kipindi1_numpy.ipynb(Ctrl+S).
The first cell (Markdown — click + Markdown):
# Kipindi 1: NumPy
Leo tunajifunza arrays.
A code cell:
import numpy as np np.__version__
Output: '2.x.x' (your version). Press Shift+Enter.
Show: (a) the number [1] beside the cell — the running order; (b) the Variables button at the top of the notebook; (c) Restart — then try running a cell other than the first → NameError: name 'np' is not defined. Say: "The kernel has forgotten. Run from the top (Run All)."
Step 2 — List versus array.
bei_list = [3000, 2800, 1500, 1000, 500] print(bei_list * 2) bei = np.array(bei_list) print(bei * 2) print(bei + 100)
Output:
[3000, 2800, 1500, 1000, 500, 3000, 2800, 1500, 1000, 500] [6000 5600 3000 2000 1000] [3100 2900 1600 1100 600]
Say: "The list repeated itself; the array multiplied every price. That is vectorized — no for."
Step 3 — Array properties and statistics.
print(type(bei), bei.dtype, bei.shape, bei.ndim) print("jumla:", bei.sum()) print("wastani:", bei.mean()) print("kubwa:", bei.max(), "ndogo:", bei.min()) print("std:", bei.std().round(1))
Output:
<class 'numpy.ndarray'> int64 (5,) 1 jumla: 8800 wastani: 1760.0 kubwa: 3000 ndogo: 500 std: 985.1
Step 4 — Boolean masks.
print(bei > 1000) print(bei[bei > 1000]) print((bei > 1000).sum(), "bidhaa zina bei zaidi ya 1000")
Output:
[ True True True False False] [3000 2800 1500] 3 bidhaa zina bei zaidi ya 1000
Explain: True counts as 1, False as 0 — that is why .sum() counts them.
Step 5 — A real example: students' marks.
alama = np.array([45, 78, 62, 90, 33, 71, 85, 55]) print("Wastani:", alama.mean()) print("Median:", np.median(alama)) print("Waliofaulu (>=50):", (alama >= 50).sum()) print(np.where(alama >= 50, "Amefaulu", "Ameshindwa")) print("Baada ya bonus 10%:", np.round(alama * 1.1).astype(int))
Output:
Wastani: 64.875 Median: 66.5 Waliofaulu (>=50): 6 ['Ameshindwa' 'Amefaulu' 'Amefaulu' 'Amefaulu' 'Ameshindwa' 'Amefaulu' 'Amefaulu' 'Amefaulu'] Baada ya bonus 10%: [50 86 68 99 36 78 94 61]
Step 6 — 2D arrays and axis. One week's temperatures for two cities:
joto = np.array([ [30, 31, 32, 31, 33, 34, 32], # Dar es Salaam [24, 25, 23, 22, 26, 27, 25], # Arusha ]) print(joto.shape) # (2, 7) -> miji 2, siku 7 print(joto[0]) # Dar, siku zote print(joto[:, 0]) # siku ya kwanza, miji yote print(joto[1, 6]) # Arusha, siku ya 7 print("Wastani kwa mji:", joto.mean(axis=1).round(1)) print("Kubwa kwa siku:", joto.max(axis=0))
Output:
(2, 7) [30 31 32 31 33 34 32] [30 24] 25 Wastani kwa mji: [31.9 24.6] Kubwa kwa siku: [30 31 32 31 33 34 32]
Draw on the board (or in a Markdown cell):
axis=1 → (kwa kila row)
┌──────────────────────┐
axis=0 │ │ 30 31 32 31 33 34 32 │ Dar
↓ │ │ 24 25 23 22 26 27 25 │ Arusha
└──────────────────────┘
Step 7 — Creating arrays quickly.
print(np.arange(12).reshape(3, 4)) print(np.zeros((2, 3))) print(np.linspace(0, 1, 5)) rng = np.random.default_rng(42) print(rng.integers(1, 7, size=10)) # kurusha kete mara 10
Output:
[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[0. 0. 0.] [0. 0. 0.]] [0. 0.25 0.5 0.75 1. ] [1 5 4 3 3 6 1 5 2 1]
Say: "The number 42 inside default_rng(42) is a 'seed' — it makes the 'random' repeat identically every time — important in ML so experiments can be compared."
Questions to ask the student
- What is the difference between a normal program and ML? — Normal: we write the rules. ML: we give data + answers, and the computer learns the rules.
- In predicting a house price, what are the features and what is the label? — Features: size, location, rooms... Label: price.
- Name one example of ML you use every day. — (Any sensible answer: YouTube recommendations, email spam filter, Google Translate...)
- What is a kernel? What happens if you restart it? — The Python that runs the notebook; a restart wipes all the variables.
[1,2,3] * 2versusnp.array([1,2,3]) * 2? — List:[1,2,3,1,2,3]. Array:[2 4 6].- How many items does an array with shape
(3, 4)have? — 12. - How many items does
joto.mean(axis=0)return for a(2, 7)array? — 7 (the average of each day/column). - Why does
(alama >= 50).sum()count those who passed? —True= 1,False= 0.
Common mistakes
| Mistake | Message / symptom | Solution |
|---|---|---|
| Kernel not chosen / it is a non-venv Python | ModuleNotFoundError: No module named 'numpy' |
"Select Kernel" → .venv; make sure of pip install numpy inside the venv |
VS Code asks to install ipykernel |
Popup: "Running cells with '.venv' requires the ipykernel package" | Click Install or pip install ipykernel |
| Running cells out of order | NameError: name 'np' is not defined |
Restart + Run All |
| Mixing types in an array | np.array([1, "2", 3.0]) → ['1' '2' '3.0'] (dtype <U32 — text!) |
Make sure the data is numbers; astype(float) |
Using and/or on arrays |
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() |
Use &, \| and brackets: (a > 1) & (a < 5) |
| Mismatched shapes | ValueError: operands could not be broadcast together with shapes (3,) (4,) |
Make sure the arrays are the same length |
An impossible reshape |
ValueError: cannot reshape array of size 12 into shape (5,3) |
The product of the dimensions = the number of items (3×4=12) |
Being surprised by np.int64(8800) |
The cell output shows np.int64(...) |
Normal in NumPy 2; use print(...) or int(...) |
In-class exercise
Exercise 1.1 — Sales over 7 days (thousands of TSh): [120, 95, 140, 80, 160, 210, 175]. With NumPy find: the total, the average, the day (index) with the highest sales (argmax), and how many days exceeded 130.
Exercise 1.2 — Convert the Celsius temperatures [30, 25, 18, 35, 22] to Fahrenheit (F = C * 9/5 + 32) without a loop.
Exercise 1.3 — Marks of 4 students in 3 subjects:
alama = np.array([[70, 85, 60], [55, 40, 75], [90, 95, 88], [65, 70, 50]])
Find: (a) each student's average, (b) each subject's average, (c) the best student (index), (d) all marks below 60.
Exercise 1.4 (think) — Write in a Markdown cell: one example of a problem in Tanzania that could be solved with ML. What are the features? What is the label?
Answer
1.1
mauzo = np.array([120, 95, 140, 80, 160, 210, 175]) print(mauzo.sum()) # 980 print(mauzo.mean()) # 140.0 print(mauzo.argmax()) # 5 (siku ya 6 -> 210) print((mauzo > 130).sum()) # 4
1.2
c = np.array([30, 25, 18, 35, 22]) print(c * 9 / 5 + 32) # [86. 77. 64.4 95. 71.6]
1.3
print(alama.mean(axis=1).round(1)) # [71.7 56.7 91. 61.7] print(alama.mean(axis=0)) # [70. 72.5 68.25] print(alama.mean(axis=1).argmax()) # 2 print(alama[alama < 60]) # [55 40 50]
1.4 (example) — Predicting a farm's maize harvest. Features: amount of rain, soil type, fertiliser used, farm size, region. Label: sacks per acre. (Other examples: predicting crop prices at the market, identifying cassava disease from a photo of a leaf, detecting fraud in mobile money transactions.)
Session 2 — pandas: Reading, Exploring, Filtering, and Cleaning Data
Session flow
- Homework review (NumPy)
- What pandas is: DataFrame and Series
- Creating
mauzo.csvandread_csv;head,shape,info,describe - Selecting: columns,
loc,iloc,value_counts,unique - Filtering by conditions (
&,\|,isin) - Cleaning:
isna,fillna,dropna,drop_duplicates,astype,to_datetime - New columns,
sort_values,groupby - In-class exercise
- Q&A + homework
Explanation for the teacher (read this first)
pandas is a library for tabular data — think "Excel inside Python", but more powerful and repeatable (reproducible).
- DataFrame — the whole table: rows and columns, each column with a name.
- Series — one column (or one row) — basically a NumPy array with an index (row labels).
- Index — the row labels (by default 0, 1, 2...).
Selecting:
df["col"]→ a Series (one column).df[["a", "b"]](double brackets!) → a DataFrame.df.loc[row_label, col_label]— by names/labels.locslicing includes the end:df.loc[2:4]= rows 2, 3, 4.df.iloc[row_pos, col_pos]— by position (numbers, like a list).df.iloc[2:4]= rows 2, 3 (the end is not included).
Filtering: df[df["idadi"] > 15] — inside is a boolean mask (like NumPy!). Multiple conditions: & (and), | (or), ~ (not), each condition in brackets.
Missing data: pandas shows it as NaN (Not a Number). A number column with NaN becomes float64 (which is why idadi (quantity) looks like 12.0).
df.isna().sum()— counts NaN per column.df["col"].fillna(thamani)— fills them in. For numbers, often the median (it isn't affected much by odd values, unlike the mean).df.dropna()— deletes rows with NaN. Use carefully — you lose data.- Important (pandas 2/3, Copy-on-Write): write
df["col"] = df["col"].fillna(...). Don't usedf["col"].fillna(..., inplace=True)— in new pandas it doesn't changedfand gives a warning.
Duplicates: df.duplicated().sum(), df.drop_duplicates().
groupby — "split, compute, combine" (split–apply–combine): df.groupby("mkoa")["jumla"].sum() = "for each region (mkoa), the total (jumla) of sales". It is like an Excel Pivot Table. agg allows several calculations at once.
Important behaviour: many pandas operations return a new DataFrame and don't change the old one. df.drop_duplicates() alone doesn't change df — you must write df = df.drop_duplicates(). This is every beginner's first mistake.
Say this to the student
"A DataFrame is like a shop's sales book: each line is one transaction, each column is one piece of information — date, product, quantity, price. A Series is just one column, for example all of 'quantity'."
"
locis finding someone by their name;ilocis finding them by their seat number in class.""Real data is usually dirty — someone forgot to write the quantity, someone else wrote a transaction twice. Cleaning data is like sorting rice before cooking: if you leave the stones in, the whole meal is ruined."
"
groupbyis like telling a clerk: 'Sort these receipts by region, then total each pile for me.'"
Type this code live (share screen)
Create a new notebook kipindi2_pandas.ipynb (kernel .venv).
Step 1 — Creating the data. The first cell (the %%writefile magic writes a file; it must be the first line of the cell):
%%writefile mauzo.csv tarehe,mkoa,bidhaa,aina,idadi,bei_moja 2027-01-04,Dar es Salaam,Sukari,Chakula,12,3000 2027-01-04,Arusha,Mchele,Chakula,8,2800 2027-01-04,Mwanza,Sabuni,Usafi,15,1500 2027-01-05,Dar es Salaam,Daftari,Shule,30,1000 2027-01-05,Dodoma,Kalamu,Shule,,500 2027-01-05,Arusha,Sukari,Chakula,10,3000 2027-01-06,Mwanza,Mchele,Chakula,20,2800 2027-01-06,Dar es Salaam,Sabuni,Usafi,9,1500 2027-01-06,,Daftari,Shule,25,1000 2027-01-07,Dodoma,Sukari,Chakula,7,3000 2027-01-07,Dar es Salaam,Kalamu,Shule,40,500 2027-01-07,Arusha,Sabuni,Usafi,,1500 2027-01-08,Mwanza,Sukari,Chakula,14,3000 2027-01-08,Dar es Salaam,Mchele,Chakula,18,2800 2027-01-08,Dar es Salaam,Mchele,Chakula,18,2800 2027-01-08,Dodoma,Daftari,Shule,12,1000
Output: Writing mauzo.csv. Open mauzo.csv in VS Code — show the student: "Look: the Dodoma Kalamu (pens) row has no quantity, one row has no region, and the Mchele (rice) line for the 8th is written twice. This is dirty data on purpose."
Step 2 — Reading and exploring.
import pandas as pd df = pd.read_csv("mauzo.csv") df.head()
Output (a nice table in the notebook):
tarehe mkoa bidhaa aina idadi bei_moja 0 2027-01-04 Dar es Salaam Sukari Chakula 12.0 3000 1 2027-01-04 Arusha Mchele Chakula 8.0 2800 2 2027-01-04 Mwanza Sabuni Usafi 15.0 1500 3 2027-01-05 Dar es Salaam Daftari Shule 30.0 1000 4 2027-01-05 Dodoma Kalamu Shule NaN 500
print(df.shape) df.info()
Output:
(16, 6) <class 'pandas.DataFrame'> RangeIndex: 16 entries, 0 to 15 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 tarehe 16 non-null str 1 mkoa 15 non-null str 2 bidhaa 16 non-null str 3 aina 16 non-null str 4 idadi 14 non-null float64 5 bei_moja 16 non-null int64 dtypes: float64(1), int64(1), str(4) memory usage: 900.0 bytes
(pandas 2.x shows object instead of str — it means the same thing: text.)
Ask: "What does info tell us about the dirty data?" → mkoa 15/16, idadi 14/16 — there are missing values.
df.describe()
Output:
idadi bei_moja count 14.000000 16.000000 mean 17.000000 1981.250000 std 9.323255 993.457766 min 7.000000 500.000000 25% 10.500000 1000.000000 50% 14.500000 2150.000000 75% 19.500000 2850.000000 max 40.000000 3000.000000
Explain 50% = median.
Step 3 — Selecting.
df["bidhaa"].head(3)
0 Sukari 1 Mchele 2 Sabuni Name: bidhaa, dtype: str
print(type(df["bidhaa"]), type(df[["bidhaa", "idadi"]])) df[["bidhaa", "idadi"]].head(3)
<class 'pandas.Series'> <class 'pandas.DataFrame'> bidhaa idadi 0 Sukari 12.0 1 Mchele 8.0 2 Sabuni 15.0
df["mkoa"].value_counts()
mkoa Dar es Salaam 6 Arusha 3 Mwanza 3 Dodoma 3 Name: count, dtype: int64
df.loc[2:4, ["mkoa", "bidhaa"]] # lebo 2 hadi 4 (4 imejumuishwa)
mkoa bidhaa 2 Mwanza Sabuni 3 Dar es Salaam Daftari 4 Dodoma Kalamu
df.iloc[-1] # row ya mwisho
tarehe 2027-01-08 mkoa Dodoma bidhaa Daftari aina Shule idadi 12.0 bei_moja 1000 Name: 15, dtype: object
Step 4 — Filtering.
df[df["idadi"] > 15]
tarehe mkoa bidhaa aina idadi bei_moja 3 2027-01-05 Dar es Salaam Daftari Shule 30.0 1000 6 2027-01-06 Mwanza Mchele Chakula 20.0 2800 8 2027-01-06 NaN Daftari Shule 25.0 1000 10 2027-01-07 Dar es Salaam Kalamu Shule 40.0 500 13 2027-01-08 Dar es Salaam Mchele Chakula 18.0 2800 14 2027-01-08 Dar es Salaam Mchele Chakula 18.0 2800
df[(df["aina"] == "Chakula") & (df["idadi"] >= 12)]
tarehe mkoa bidhaa aina idadi bei_moja 0 2027-01-04 Dar es Salaam Sukari Chakula 12.0 3000 6 2027-01-06 Mwanza Mchele Chakula 20.0 2800 12 2027-01-08 Mwanza Sukari Chakula 14.0 3000 13 2027-01-08 Dar es Salaam Mchele Chakula 18.0 2800 14 2027-01-08 Dar es Salaam Mchele Chakula 18.0 2800
df[df["mkoa"].isin(["Arusha", "Dodoma"])].shape
(6, 6)
Step 5 — Cleaning.
df.isna().sum()
tarehe 0 mkoa 1 bidhaa 0 aina 0 idadi 2 bei_moja 0 dtype: int64
df[df.isna().any(axis=1)] # rows zenye tatizo
tarehe mkoa bidhaa aina idadi bei_moja 4 2027-01-05 Dodoma Kalamu Shule NaN 500 8 2027-01-06 NaN Daftari Shule 25.0 1000 11 2027-01-07 Arusha Sabuni Usafi NaN 1500
print("Duplicates:", df.duplicated().sum()) df = df.drop_duplicates() median_idadi = df["idadi"].median() print("Median ya idadi:", median_idadi) df["idadi"] = df["idadi"].fillna(median_idadi) df["mkoa"] = df["mkoa"].fillna("Haijulikani") df["idadi"] = df["idadi"].astype(int) df["tarehe"] = pd.to_datetime(df["tarehe"]) print("NaN zilizobaki:", df.isna().sum().sum()) print(df.shape)
Duplicates: 1 Median ya idadi: 14.0 NaN zilizobaki: 0 (15, 6)
Discuss: "Is filling the quantity with the median always right? No — it is a decision. It might have been better to ask the shop owner. In data science, these decisions must be written down (a Markdown cell!)."
Step 6 — New column, sort, groupby.
df["jumla"] = df["idadi"] * df["bei_moja"] df.sort_values("jumla", ascending=False).head(3)
tarehe mkoa bidhaa aina idadi bei_moja jumla 6 2027-01-06 Mwanza Mchele Chakula 20 2800 56000 13 2027-01-08 Dar es Salaam Mchele Chakula 18 2800 50400 12 2027-01-08 Mwanza Sukari Chakula 14 3000 42000
df.groupby("mkoa")["jumla"].sum().sort_values(ascending=False)
mkoa Dar es Salaam 149900 Mwanza 120500 Arusha 73400 Dodoma 40000 Haijulikani 25000 Name: jumla, dtype: int64
df.groupby("aina").agg( mauzo=("jumla", "sum"), wastani_idadi=("idadi", "mean"), miamala=("jumla", "count"), )
mauzo wastani_idadi miamala aina Chakula 257800 12.714286 7 Shule 94000 24.200000 5 Usafi 57000 12.666667 3
print("Mauzo yote:", df["jumla"].sum()) df.to_csv("mauzo_safi.csv", index=False)
Mauzo yote: 408800
Say: "We have saved the clean data — in Session 3 we will plot it."
Questions to ask the student
- What is the difference between
df["idadi"]anddf[["idadi"]]? — The first is a Series, the second a DataFrame (with one column). - How many rows does
df.loc[2:4]return?df.iloc[2:4]? —loc: 3 (2,3,4).iloc: 2 (2,3). - Why was the
idadicolumn12.0(float) at first? — Because it hasNaN, andNaNis a float. - Why do we write
df = df.drop_duplicates()? — It returns a new DataFrame; without saving it,dfdoesn't change. - Why fill with the median rather than the mean? — The median isn't pulled much by odd values (for example Kalamu 40).
- How do you filter "Chakula OR Usafi" (food or cleaning)? —
df[(df["aina"] == "Chakula") | (df["aina"] == "Usafi")]ordf[df["aina"].isin(["Chakula", "Usafi"])]. groupby("mkoa")["jumla"].sum()in words? — "For each region, add up the sales."
Common mistakes
| Mistake | Message / symptom | Solution |
|---|---|---|
| Wrong column name (capital letters/spaces) | KeyError: 'Idadi' |
df.columns to see the real names |
| File missing / a different folder | FileNotFoundError: [Errno 2] No such file or directory: 'mauzo.csv' |
A notebook starts in its own folder; make sure the CSV is there |
and instead of & |
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all(). |
(sharti1) & (sharti2) |
| Forgetting brackets around conditions | TypeError: Cannot perform 'rand_' with a dtyped [int64] array and scalar of type [bool] (or a similar message) |
(df["a"] > 1) & (df["b"] < 5) |
df.drop_duplicates() without saving |
The shape doesn't change | df = df.drop_duplicates() |
fillna(..., inplace=True) on a column |
FutureWarning/ChainedAssignmentError and the data doesn't change |
df["col"] = df["col"].fillna(...) |
astype(int) before filling NaN |
IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer |
Fill NaN first |
%%writefile not on the first line |
UsageError: Line magic function%%writefilenot found. |
%%writefile must be the first line of the cell |
| Changing a "copy" of a filtered DataFrame | SettingWithCopyWarning (pandas 2) / changes don't appear |
Use .copy(): chakula = df[df["aina"] == "Chakula"].copy() |
In-class exercise
Use the clean df (or pd.read_csv("mauzo_safi.csv", parse_dates=["tarehe"])).
Exercise 2.1 — Show all Dar es Salaam transactions where jumla (total) is more than 20,000.
Exercise 2.2 — Which product sold the largest total quantity? (hint: groupby + idxmax)
Exercise 2.3 — Create a column ukubwa (size) with "Kubwa" (large) if jumla >= 30000, otherwise "Ndogo" (small) (hint: np.where). Count how many transactions each group has.
Exercise 2.4 — Total sales for each date. Which day was the best?
Answer
import numpy as np # 2.1 df[(df["mkoa"] == "Dar es Salaam") & (df["jumla"] > 20000)]
(The Dar Kalamu row has a total of exactly 20000, so it isn't included: > not >=.)
Output:
tarehe mkoa bidhaa aina idadi bei_moja jumla 0 2027-01-04 Dar es Salaam Sukari Chakula 12 3000 36000 3 2027-01-05 Dar es Salaam Daftari Shule 30 1000 30000 13 2027-01-08 Dar es Salaam Mchele Chakula 18 2800 50400
# 2.2 df.groupby("bidhaa")["idadi"].sum().idxmax() # 'Daftari' (30 + 25 + 12 = 67) # 2.3 df["ukubwa"] = np.where(df["jumla"] >= 30000, "Kubwa", "Ndogo") df["ukubwa"].value_counts() # Ndogo 9 # Kubwa 6 # 2.4 kwa_siku = df.groupby("tarehe")["jumla"].sum() print(kwa_siku) print("Siku bora:", kwa_siku.idxmax().date(), "->", kwa_siku.max())
Output of 2.4:
tarehe 2027-01-04 80900 2027-01-05 67000 2027-01-06 94500 2027-01-07 62000 2027-01-08 104400 Name: jumla, dtype: int64 Siku bora: 2027-01-08 -> 104400
(The "Kubwa" transactions are: 56000, 50400, 42000, 36000, 30000, 30000 = 6.)
Session 3 — matplotlib: Plotting Data, and a First "Small ML"
Session flow
- Homework review
- Why plot? Types of chart and when to use each one
fig, ax = plt.subplots(); a line plot of sales per day; titles, labels,savefig- Bar (vertical and horizontal), histogram, scatter
- pandas'
df.plot(...); two subplots together - "Small ML": study hours → marks;
np.polyfit; predicting; drawing the line - In-class exercise
- Q&A, closing Month 3, homework
Explanation for the teacher (read this first)
Why plot? Human eyes see trends, differences, and odd values (outliers) much faster in a picture than in a table.
| Question | Chart |
|---|---|
| How does something change over time? | Line |
| Comparing groups (regions, products) | Bar (barh if the names are long) |
| How is the data spread out (distribution)? | Histogram |
| Are two things related? | Scatter |
matplotlib — the style we will use ("object-oriented"):
fig, ax = plt.subplots() # fig = karatasi nzima, ax = eneo la chati moja ax.plot(x, y) ax.set_title(...); ax.set_xlabel(...); ax.set_ylabel(...) fig.savefig("picha.png")
In a notebook, the chart appears by itself below the cell. In a normal script (.py) you need plt.show().
pandas has a shortcut: series.plot(kind="bar") — inside it uses matplotlib and returns ax.
"Small ML" — linear regression by hand. We have student data: study hours (the feature) and marks (the label). We want a line alama = a × masaa + b (marks = a × hours + b) that passes as close as possible to all the points. np.polyfit(x, y, 1) finds the best a (slope) and b (intercept) by "least squares" (it minimises the sum of squared errors). This is "training": from the data, we got two numbers (a, b) — that is the model. Then "prediction": a * 10 + b for a student who studied 10 hours.
- This is ML in the full sense (supervised learning, regression) — just very small. Big models (like those of ChatGPT) have billions of numbers instead of two, but the idea is the same: adjust the numbers so that the errors on the data become small.
- A teaching caution: predicting outside the data (for example 30 hours → marks of 229!) is dangerous — the model doesn't know that marks don't go above 100. This is a good lesson about the limits of models.
- Optional:
np.corrcoef(x, y)— a number between -1 and 1 showing the strength of a linear relationship.
Next week (Week 13) the student will use scikit-learn, which does this and more; today is just a taste.
Say this to the student
"A table is like reading a weather report in numbers; a chart is like looking at a cloud map — you understand right away."
"
figis the whole board,axis the part of the board you draw on. You can divide one board into two parts (subplots).""Imagine 8 students. The more hours they study, the more their marks rise. If we draw a line through the middle of their points, we can estimate a ninth student's mark. Drawing that line with maths is what 'training a model' means. The two numbers we got are the 'model' itself."
"But this model doesn't know that marks don't go above 100 — give it 30 hours and it will say 229! Models only know what the data taught them. Remember this your whole AI life."
Type this code live (share screen)
New notebook kipindi3_matplotlib.ipynb. Make sure mauzo_safi.csv (from Session 2) is in this folder.
Step 1 — Setup.
import matplotlib.pyplot as plt import numpy as np import pandas as pd df = pd.read_csv("mauzo_safi.csv", parse_dates=["tarehe"]) df.head(3)
parse_dates converts tarehe (date) to dates directly (like pd.to_datetime).
Step 2 — Line plot.
kwa_siku = df.groupby("tarehe")["jumla"].sum() fig, ax = plt.subplots(figsize=(8, 4)) ax.plot(kwa_siku.index, kwa_siku.values, marker="o") ax.set_title("Mauzo ya jumla kwa siku") ax.set_xlabel("Tarehe") ax.set_ylabel("Mauzo (TSh)") ax.grid(True) fig.autofmt_xdate() # inazungusha tarehe zisigongane fig.savefig("mauzo_kwa_siku.png", dpi=120, bbox_inches="tight")
Result: a line with 5 points — high on the 6th and the 8th, low on the 7th. Show the mauzo_kwa_siku.png file in the Explorer. Ask: "On which day did sales drop? Why do you think so?"
Step 3 — Bar plot (horizontal, sorted).
kwa_mkoa = df.groupby("mkoa")["jumla"].sum().sort_values() fig, ax = plt.subplots(figsize=(7, 4)) ax.barh(kwa_mkoa.index, kwa_mkoa.values, color="tab:green") ax.set_title("Mauzo kwa mkoa") ax.set_xlabel("Mauzo (TSh)")
Result: Dar es Salaam at the top (149,900), Haijulikani (unknown) at the bottom (25,000). Say: "Sorting (sort_values) makes the chart readable at a glance."
Step 4 — Histogram.
fig, ax = plt.subplots() ax.hist(df["idadi"], bins=5, edgecolor="black") ax.set_title("Mgawanyo wa idadi kwa muamala") ax.set_xlabel("Idadi") ax.set_ylabel("Miamala mingapi")
Explain: "Most transactions are between 7 and 20; a few are higher, and one (Kalamu 40) is far from the others — that is an 'outlier'."
Step 5 — Scatter.
fig, ax = plt.subplots() ax.scatter(df["bei_moja"], df["idadi"]) ax.set_title("Bei dhidi ya idadi") ax.set_xlabel("Bei ya kipande (TSh)") ax.set_ylabel("Idadi iliyouzwa")
Ask: "Do you see a relationship? Cheaper products (Kalamu, Daftari — pens, exercise books) sell in larger quantities."
Step 6 — pandas .plot and two subplots.
ax = df.groupby("aina")["jumla"].sum().plot(kind="bar", title="Mauzo kwa aina", rot=0) ax.set_ylabel("TSh")
kwa_aina = df.groupby("aina")["jumla"].sum() fig, axes = plt.subplots(1, 2, figsize=(11, 4)) axes[0].bar(kwa_mkoa.index, kwa_mkoa.values) axes[0].set_title("Kwa mkoa") axes[0].tick_params(axis="x", rotation=45) axes[1].pie(kwa_aina.values, labels=kwa_aina.index, autopct="%1.0f%%") axes[1].set_title("Sehemu ya kila aina") fig.tight_layout() fig.savefig("ripoti.png", dpi=120)
Pie: Chakula (food) 63%, Shule (school) 23%, Usafi (cleaning) 14%.
Step 7 — "Small ML": predicting marks. A Markdown cell first:
## Model yetu ya kwanza Swali: Mwanafunzi akisoma masaa X kwa wiki, atapata alama ngapi? - Feature: masaa - Label: alama
masaa = np.array([1, 2, 3, 4, 5, 6, 7, 8]) alama = np.array([35, 42, 50, 55, 61, 70, 74, 83]) # "TRAINING": tafuta mstari bora alama = a * masaa + b a, b = np.polyfit(masaa, alama, 1) print(f"Model: alama = {a:.2f} x masaa + {b:.2f}") print("Uhusiano (correlation):", np.corrcoef(masaa, alama)[0, 1].round(3)) # "PREDICTION" for m in [2.5, 10]: print(f"Masaa {m} -> alama inayotarajiwa {a * m + b:.1f}")
Output:
Model: alama = 6.69 x masaa + 28.64 Uhusiano (correlation): 0.998 Masaa 2.5 -> alama inayotarajiwa 45.4 Masaa 10 -> alama inayotarajiwa 95.5
x_mstari = np.linspace(0, 10, 50) fig, ax = plt.subplots() ax.scatter(masaa, alama, label="Data halisi") ax.plot(x_mstari, a * x_mstari + b, color="red", label="Model (mstari)") ax.scatter([10], [a * 10 + b], color="orange", s=100, marker="*", label="Utabiri: masaa 10") ax.set_xlabel("Masaa ya kusoma kwa wiki") ax.set_ylabel("Alama") ax.set_title("Model ya kwanza: Linear Regression") ax.legend()
Then ask: "30 hours?" → a * 30 + b = 229.3. "Is that possible?" → No! The model doesn't know the limits. The lesson: don't trust a model outside the data it was trained on.
Close the month: "This month you have built an API (FastAPI) and started on data (NumPy/pandas/matplotlib). Next week we begin Month 4 with Machine Learning using scikit-learn: we will train real models from data like this."
Questions to ask the student
- Which chart would you use to show monthly sales over a whole year? — Line.
- To compare the number of students across 10 schools? — Bar (or barh).
- To see how the ages of 1000 customers are spread out? — Histogram.
- What are
figandax? —figis the whole picture;axis one chart inside it. - In
np.polyfit(masaa, alama, 1), where is the "training" and what is the "model"? — The training ispolyfitfindingaandb; the model is those two numbers (the line). - Why can't the prediction for 30 hours be trusted? — It is outside the data used for training; the model doesn't know marks don't go above 100.
- Why do we use
savefigbefore showing? — To save the picture for a report; (in a script,plt.show()comes aftersavefig, because after show the figure may be empty).
Common mistakes
| Mistake | Message / symptom | Solution |
|---|---|---|
| matplotlib not installed in the kernel | ModuleNotFoundError: No module named 'matplotlib' |
pip install matplotlib inside .venv; restart the kernel |
In a .py script the chart doesn't appear |
The script ends with no window | Add plt.show() at the end |
savefig after plt.show() in a script |
A blank white picture | savefig first, show afterwards |
| x and y of different lengths | ValueError: x and y must have same first dimension, but have shapes (5,) and (4,) |
Make sure they come from the same data |
| Axis labels overlapping | The text is squeezed together | fig.autofmt_xdate(), tick_params(rotation=45), or barh |
ax.title("...") |
TypeError: 'Text' object is not callable |
ax.set_title("...") (and set_xlabel, set_ylabel) |
Mixing plt.title and ax.set_title in subplots |
The title goes on the wrong chart | With fig, axes, use axes[i].set_title(...) |
parse_dates forgotten |
Dates are sorted as text; the axis gets crowded | pd.read_csv(..., parse_dates=["tarehe"]) |
In-class exercise
Exercise 3.1 — Draw a bar chart of the quantity (not the total) sold per product, sorted from largest to smallest, with a title and labels. Save it as bidhaa.png.
Exercise 3.2 — Draw a line plot of sales per day, but with two lines on one chart: "Chakula" and "Shule" (hint: filter first, groupby("tarehe") for each, label=, ax.legend()).
Exercise 3.3 — Maize price data (TSh per kilo) over 6 months: miezi = [1, 2, 3, 4, 5, 6], bei = [700, 750, 780, 820, 870, 900]. Train a line model, predict months 7 and 8, and plot the data + line + prediction.
Answer
3.1
kwa_bidhaa = df.groupby("bidhaa")["idadi"].sum().sort_values(ascending=False) fig, ax = plt.subplots() ax.bar(kwa_bidhaa.index, kwa_bidhaa.values, color="tab:blue") ax.set_title("Idadi iliyouzwa kwa bidhaa") ax.set_xlabel("Bidhaa") ax.set_ylabel("Idadi") fig.savefig("bidhaa.png", dpi=120, bbox_inches="tight")
The order of the bars: Daftari 67, Kalamu 54, Mchele 46, Sukari 43, Sabuni 38.
3.2
fig, ax = plt.subplots(figsize=(8, 4)) for aina in ["Chakula", "Shule"]: sehemu = df[df["aina"] == aina].groupby("tarehe")["jumla"].sum() ax.plot(sehemu.index, sehemu.values, marker="o", label=aina) ax.set_title("Mauzo kwa siku: Chakula dhidi ya Shule") ax.set_ylabel("TSh") ax.legend() fig.autofmt_xdate()
3.3
miezi = np.array([1, 2, 3, 4, 5, 6]) bei = np.array([700, 750, 780, 820, 870, 900]) a, b = np.polyfit(miezi, bei, 1) print(f"bei = {a:.1f} x mwezi + {b:.1f}") # bei = 40.0 x mwezi + 663.3 for m in [7, 8]: print(f"Mwezi {m}: TSh {a * m + b:.0f}") # Mwezi 7: TSh 943 ; Mwezi 8: TSh 983 x = np.linspace(1, 8, 50) fig, ax = plt.subplots() ax.scatter(miezi, bei, label="Bei halisi") ax.plot(x, a * x + b, color="red", label="Model") ax.scatter([7, 8], [a * 7 + b, a * 8 + b], color="orange", marker="*", s=120, label="Utabiri") ax.set_xlabel("Mwezi") ax.set_ylabel("TSh kwa kilo") ax.set_title("Bei ya mahindi") ax.legend()
Homework
To submit: next session.
"Analysis of Student Results." The first cell should write this CSV with %%writefile wanafunzi.csv:
jina,jinsia,shule,hisabati,kiingereza,sayansi,masaa_ya_kusoma Amina,F,Azania,78,85,80,10 Baraka,M,Azania,55,60,,6 Neema,F,Jangwani,92,88,95,14 Juma,M,Jangwani,40,52,45,3 Zawadi,F,Azania,66,,70,8 Hassani,M,Kibasila,71,65,68,9 Rehema,F,Kibasila,85,90,82,12 Faraji,M,Kibasila,35,45,40,2 Mwanaidi,F,Jangwani,59,70,62,7 Omari,M,Azania,88,75,91,13 Omari,M,Azania,88,75,91,13
Tasks:
- Read it, show
shape,info(), and the number of NaN per column. - Remove duplicates. Fill in missing marks with the average for that subject (rounded to a whole number).
- Add a column
wastani(average; the average of the 3 subjects, 1 decimal place) anddaraja(grade): A (≥80), B (≥65), C (≥50), F (<50). - The average of
wastaniper school and per gender. - The top 3 students (name and average).
- 3 charts: a bar of the average per school; a histogram of
wastani; a scatter ofmasaa_ya_kusoma(study hours) againstwastani. - Train a line model (
np.polyfit) of hours → average; predict for 5 and 11 hours; draw the line on the scatter. - A Markdown cell at the end: a 3–4 sentence conclusion.
Answer
# Cell 1 %%writefile wanafunzi.csv jina,jinsia,shule,hisabati,kiingereza,sayansi,masaa_ya_kusoma Amina,F,Azania,78,85,80,10 Baraka,M,Azania,55,60,,6 Neema,F,Jangwani,92,88,95,14 Juma,M,Jangwani,40,52,45,3 Zawadi,F,Azania,66,,70,8 Hassani,M,Kibasila,71,65,68,9 Rehema,F,Kibasila,85,90,82,12 Faraji,M,Kibasila,35,45,40,2 Mwanaidi,F,Jangwani,59,70,62,7 Omari,M,Azania,88,75,91,13 Omari,M,Azania,88,75,91,13
# Cell 2 — 1 import matplotlib.pyplot as plt import numpy as np import pandas as pd df = pd.read_csv("wanafunzi.csv") print(df.shape) # (11, 7) df.info() df.isna().sum() # kiingereza 1, sayansi 1
# Cell 3 — 2 df = df.drop_duplicates() # (10, 7) masomo = ["hisabati", "kiingereza", "sayansi"] for somo in masomo: df[somo] = df[somo].fillna(round(df[somo].mean())) df[masomo] = df[masomo].astype(int) df.isna().sum().sum() # 0
(Zawadi's Kiingereza (English) mark = the average of the other 9 = 70; Baraka's Sayansi (Science) mark = 70.)
# Cell 4 — 3 df["wastani"] = df[masomo].mean(axis=1).round(1) def daraja(w: float) -> str: if w >= 80: return "A" if w >= 65: return "B" if w >= 50: return "C" return "F" df["daraja"] = df["wastani"].apply(daraja) df[["jina", "wastani", "daraja"]]
Output:
jina wastani daraja 0 Amina 81.0 A 1 Baraka 61.7 C 2 Neema 91.7 A 3 Juma 45.7 F 4 Zawadi 68.7 B 5 Hassani 68.0 B 6 Rehema 85.7 A 7 Faraji 40.0 F 8 Mwanaidi 63.7 C 9 Omari 84.7 A
# Cell 5 — 4 na 5 print(df.groupby("shule")["wastani"].mean().round(1)) print(df.groupby("jinsia")["wastani"].mean().round(1)) df.nlargest(3, "wastani")[["jina", "wastani"]]
Output:
shule
Azania 74.0
Jangwani 67.0
Kibasila 64.6
Name: wastani, dtype: float64
jinsia
F 78.2
M 60.0
Name: wastani, dtype: float64
jina wastani
2 Neema 91.7
6 Rehema 85.7
9 Omari 84.7
# Cell 6 — 6 fig, axes = plt.subplots(1, 3, figsize=(15, 4)) kwa_shule = df.groupby("shule")["wastani"].mean().sort_values(ascending=False) axes[0].bar(kwa_shule.index, kwa_shule.values, color="tab:purple") axes[0].set_title("Wastani kwa shule") axes[0].set_ylabel("Wastani") axes[1].hist(df["wastani"], bins=5, edgecolor="black") axes[1].set_title("Mgawanyo wa wastani") axes[1].set_xlabel("Wastani") axes[2].scatter(df["masaa_ya_kusoma"], df["wastani"]) axes[2].set_title("Masaa dhidi ya wastani") axes[2].set_xlabel("Masaa ya kusoma") axes[2].set_ylabel("Wastani") fig.tight_layout() fig.savefig("chati_wanafunzi.png", dpi=120)
# Cell 7 — 7 x = df["masaa_ya_kusoma"].to_numpy() y = df["wastani"].to_numpy() a, b = np.polyfit(x, y, 1) print(f"wastani = {a:.2f} x masaa + {b:.2f}") for m in [5, 11]: print(f"Masaa {m}: wastani unaotarajiwa {a * m + b:.1f}") xs = np.linspace(x.min(), x.max(), 50) fig, ax = plt.subplots() ax.scatter(x, y, label="Wanafunzi") ax.plot(xs, a * xs + b, color="red", label="Model") ax.set_xlabel("Masaa ya kusoma") ax.set_ylabel("Wastani") ax.legend()
Output:
wastani = 4.20 x masaa + 33.83 Masaa 5: wastani unaotarajiwa 54.8 Masaa 11: wastani unaotarajiwa 80.0
Cell 8 — Conclusion (example): Azania has the highest average (74.0). Girls have a higher average than boys in this small sample (78.2 versus 60.0). There is a strong relationship between study hours and average — each extra hour adds about 4 marks. The sample is small (10 students), so these conclusions aren't certain for a whole school.
If you are asked a question you don't know
Data science is very broad — nobody knows every function. Say: "I don't remember the name of that function — let's look it up", then search in English: "pandas how to ...", "matplotlib rotate x labels", "numpy difference axis 0 axis 1". In a notebook you can also type df.groupby? or help(pd.read_csv) to see the description.
Official sources: NumPy https://numpy.org/doc/stable/ · pandas https://pandas.pydata.org/docs/ · matplotlib https://matplotlib.org/stable/ · VS Code Jupyter https://code.visualstudio.com/docs/datascience/jupyter-notebooks · Google ML Crash Course https://developers.google.com/machine-learning/crash-course
End-of-week checklist
- Explains AI, ML, data, features, label, training, model, prediction with their own example
- Creates a notebook, chooses the
.venvkernel, uses Markdown, restarts and uses Run All - Creates NumPy arrays and reads
shape,dtype,ndim - Uses vectorized operations and boolean masks (
a[a > x],&,|) - Understands
axis=0andaxis=1 - Reads a CSV with
pd.read_csvand explores it withhead,info,describe,value_counts - Tells apart
df["a"]/df[["a"]]andloc/iloc - Filters with multiple conditions and
isin - Cleans data:
isna,fillna,dropna,drop_duplicates,astype,to_datetime— and remembersdf = ... - Uses
groupby+sum/mean/count/agg - Draws line, bar, histogram, scatter with titles and labels, and
savefig - Has used
np.polyfitto train a small model and explains why predicting outside the data is dangerous - Homework is on GitHub and the notebook outputs are visible