Contents

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.

  1. Read:
  2. Install:

    • VS Code extension: Jupyter (publisher: Microsoft). It comes with "Jupyter Keymap", "Jupyter Renderers" and so on.
    • In this week's new venv:
    powershell
    cd $HOME\Documents
    mkdir data_wiki12
    cd data_wiki12
    python -m venv .venv
    .\.venv\Scripts\Activate.ps1
    pip install ipykernel numpy pandas matplotlib
    
  3. 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), and type(df) as pandas.DataFrame (pandas 2: pandas.core.frame.DataFrame). Don't be alarmed.

  4. Try first: create a notebook, choose the .venv kernel, run the cell import pandas as pd; pd.__version__. If you see a version, you are ready.
  5. 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...)

  1. Explain in simple words AI, Machine Learning, model, training and prediction, and why data is the foundation.
  2. Create and use a Jupyter notebook inside VS Code (cells, kernel, Markdown, restart).
  3. Create NumPy arrays, read shape/dtype, do vectorized operations, boolean masks and axis.
  4. Read a CSV with pandas and explore the data (head, info, describe, value_counts).
  5. Select columns/rows ([], loc, iloc), filter by conditions, add columns, sort (sort_values).
  6. Clean data: isna, fillna, dropna, drop_duplicates, astype, to_datetime.
  7. Use groupby + sum/mean/count + agg.
  8. Draw line, bar, histogram and scatter plots with matplotlib, and save images.
  9. See "small ML" in practice: fitting a line (np.polyfit) and predicting.

Session 1 — AI/ML in Simple Language, Jupyter, and NumPy

Session flow

  1. Homework review (Todo API: priority, overdue, delete me)
  2. What is AI? What is ML? (a conversation + everyday examples)
  3. Jupyter in VS Code: creating a notebook, kernel, cells, Markdown
  4. NumPy: why? array versus list; shape, dtype
  5. Vectorized operations, statistics (sum, mean...), boolean masks
  6. 2D arrays, axis, reshape
  7. In-class exercise
  8. 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 * 2 multiplies every element — no for loop. With a list, [1,2,3] * 2 repeats the list!
  • shape — the dimensions: (5,) is 1D with 5 items; (2, 3) is 2D, 2 rows, 3 columns.
  • Boolean mask: a > 1000 returns an array of True/False; a[a > 1000] selects those that are True.
  • 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 see np.int64(8800) — that is just an ordinary number showing its type. print(a.sum()) shows 8800.

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.

  1. VS Code → open the folder data_wiki12.
  2. Ctrl+Shift+P → type "Create: New Jupyter Notebook" → Enter.
  3. Top right, click "Select Kernel" → "Python Environments" → choose .venv.
  4. Save as kipindi1_numpy.ipynb (Ctrl+S).

The first cell (Markdown — click + Markdown):

markdown
# Kipindi 1: NumPy
Leo tunajifunza arrays.

A code cell:

python
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.

python
bei_list = [3000, 2800, 1500, 1000, 500]
print(bei_list * 2)

bei = np.array(bei_list)
print(bei * 2)
print(bei + 100)

Output:

text
[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.

python
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:

text
<class 'numpy.ndarray'> int64 (5,) 1
jumla: 8800
wastani: 1760.0
kubwa: 3000 ndogo: 500
std: 985.1

Step 4 — Boolean masks.

python
print(bei > 1000)
print(bei[bei > 1000])
print((bei > 1000).sum(), "bidhaa zina bei zaidi ya 1000")

Output:

text
[ 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.

python
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:

text
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:

python
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:

text
(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):

text
            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.

python
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:

text
[[ 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

  1. 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.
  2. In predicting a house price, what are the features and what is the label? — Features: size, location, rooms... Label: price.
  3. Name one example of ML you use every day. — (Any sensible answer: YouTube recommendations, email spam filter, Google Translate...)
  4. What is a kernel? What happens if you restart it? — The Python that runs the notebook; a restart wipes all the variables.
  5. [1,2,3] * 2 versus np.array([1,2,3]) * 2? — List: [1,2,3,1,2,3]. Array: [2 4 6].
  6. How many items does an array with shape (3, 4) have? — 12.
  7. How many items does joto.mean(axis=0) return for a (2, 7) array? — 7 (the average of each day/column).
  8. 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:

python
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

python
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

python
c = np.array([30, 25, 18, 35, 22])
print(c * 9 / 5 + 32)          # [86.  77.  64.4 95.  71.6]

1.3

python
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

  1. Homework review (NumPy)
  2. What pandas is: DataFrame and Series
  3. Creating mauzo.csv and read_csv; head, shape, info, describe
  4. Selecting: columns, loc, iloc, value_counts, unique
  5. Filtering by conditions (&, \|, isin)
  6. Cleaning: isna, fillna, dropna, drop_duplicates, astype, to_datetime
  7. New columns, sort_values, groupby
  8. In-class exercise
  9. 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. loc slicing 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 use df["col"].fillna(..., inplace=True) — in new pandas it doesn't change df and 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'."

"loc is finding someone by their name; iloc is 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."

"groupby is 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):

python
%%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.

python
import pandas as pd

df = pd.read_csv("mauzo.csv")
df.head()

Output (a nice table in the notebook):

text
       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
python
print(df.shape)
df.info()

Output:

text
(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.

python
df.describe()

Output:

text
           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.

python
df["bidhaa"].head(3)
text
0    Sukari
1    Mchele
2    Sabuni
Name: bidhaa, dtype: str
python
print(type(df["bidhaa"]), type(df[["bidhaa", "idadi"]]))
df[["bidhaa", "idadi"]].head(3)
text
<class 'pandas.Series'> <class 'pandas.DataFrame'>
   bidhaa  idadi
0  Sukari   12.0
1  Mchele    8.0
2  Sabuni   15.0
python
df["mkoa"].value_counts()
text
mkoa
Dar es Salaam    6
Arusha           3
Mwanza           3
Dodoma           3
Name: count, dtype: int64
python
df.loc[2:4, ["mkoa", "bidhaa"]]      # lebo 2 hadi 4 (4 imejumuishwa)
text
            mkoa   bidhaa
2         Mwanza   Sabuni
3  Dar es Salaam  Daftari
4         Dodoma   Kalamu
python
df.iloc[-1]                          # row ya mwisho
text
tarehe      2027-01-08
mkoa            Dodoma
bidhaa         Daftari
aina             Shule
idadi             12.0
bei_moja          1000
Name: 15, dtype: object

Step 4 — Filtering.

python
df[df["idadi"] > 15]
text
        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
python
df[(df["aina"] == "Chakula") & (df["idadi"] >= 12)]
text
        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
python
df[df["mkoa"].isin(["Arusha", "Dodoma"])].shape
text
(6, 6)

Step 5 — Cleaning.

python
df.isna().sum()
text
tarehe      0
mkoa        1
bidhaa      0
aina        0
idadi       2
bei_moja    0
dtype: int64
python
df[df.isna().any(axis=1)]          # rows zenye tatizo
text
        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
python
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)
text
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.

python
df["jumla"] = df["idadi"] * df["bei_moja"]
df.sort_values("jumla", ascending=False).head(3)
text
       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
python
df.groupby("mkoa")["jumla"].sum().sort_values(ascending=False)
text
mkoa
Dar es Salaam    149900
Mwanza           120500
Arusha            73400
Dodoma            40000
Haijulikani       25000
Name: jumla, dtype: int64
python
df.groupby("aina").agg(
    mauzo=("jumla", "sum"),
    wastani_idadi=("idadi", "mean"),
    miamala=("jumla", "count"),
)
text
          mauzo  wastani_idadi  miamala
aina                                   
Chakula  257800      12.714286        7
Shule     94000      24.200000        5
Usafi     57000      12.666667        3
python
print("Mauzo yote:", df["jumla"].sum())
df.to_csv("mauzo_safi.csv", index=False)
text
Mauzo yote: 408800

Say: "We have saved the clean data — in Session 3 we will plot it."

Questions to ask the student

  1. What is the difference between df["idadi"] and df[["idadi"]]? — The first is a Series, the second a DataFrame (with one column).
  2. How many rows does df.loc[2:4] return? df.iloc[2:4]? — loc: 3 (2,3,4). iloc: 2 (2,3).
  3. Why was the idadi column 12.0 (float) at first? — Because it has NaN, and NaN is a float.
  4. Why do we write df = df.drop_duplicates()? — It returns a new DataFrame; without saving it, df doesn't change.
  5. Why fill with the median rather than the mean? — The median isn't pulled much by odd values (for example Kalamu 40).
  6. How do you filter "Chakula OR Usafi" (food or cleaning)? — df[(df["aina"] == "Chakula") | (df["aina"] == "Usafi")] or df[df["aina"].isin(["Chakula", "Usafi"])].
  7. 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
python
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:

text
       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
python
# 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:

text
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

  1. Homework review
  2. Why plot? Types of chart and when to use each one
  3. fig, ax = plt.subplots(); a line plot of sales per day; titles, labels, savefig
  4. Bar (vertical and horizontal), histogram, scatter
  5. pandas' df.plot(...); two subplots together
  6. "Small ML": study hours → marks; np.polyfit; predicting; drawing the line
  7. In-class exercise
  8. 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"):

python
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."

"fig is the whole board, ax is 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.

python
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.

python
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).

python
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.

python
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.

python
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.

python
ax = df.groupby("aina")["jumla"].sum().plot(kind="bar", title="Mauzo kwa aina", rot=0)
ax.set_ylabel("TSh")
python
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:

markdown
## Model yetu ya kwanza
Swali: Mwanafunzi akisoma masaa X kwa wiki, atapata alama ngapi?
- Feature: masaa
- Label: alama
python
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:

text
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
python
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

  1. Which chart would you use to show monthly sales over a whole year? — Line.
  2. To compare the number of students across 10 schools? — Bar (or barh).
  3. To see how the ages of 1000 customers are spread out? — Histogram.
  4. What are fig and ax? — fig is the whole picture; ax is one chart inside it.
  5. In np.polyfit(masaa, alama, 1), where is the "training" and what is the "model"? — The training is polyfit finding a and b; the model is those two numbers (the line).
  6. 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.
  7. Why do we use savefig before showing? — To save the picture for a report; (in a script, plt.show() comes after savefig, 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

python
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

python
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

python
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:

text
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:

  1. Read it, show shape, info(), and the number of NaN per column.
  2. Remove duplicates. Fill in missing marks with the average for that subject (rounded to a whole number).
  3. Add a column wastani (average; the average of the 3 subjects, 1 decimal place) and daraja (grade): A (≥80), B (≥65), C (≥50), F (<50).
  4. The average of wastani per school and per gender.
  5. The top 3 students (name and average).
  6. 3 charts: a bar of the average per school; a histogram of wastani; a scatter of masaa_ya_kusoma (study hours) against wastani.
  7. Train a line model (np.polyfit) of hours → average; predict for 5 and 11 hours; draw the line on the scatter.
  8. A Markdown cell at the end: a 3–4 sentence conclusion.
Answer
python
# 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
python
# 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
python
# 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.)

python
# 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:

text
       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
python
# 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:

text
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
python
# 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)
python
# 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:

text
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 .venv kernel, 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=0 and axis=1
  • Reads a CSV with pd.read_csv and explores it with head, info, describe, value_counts
  • Tells apart df["a"] / df[["a"]] and loc / iloc
  • Filters with multiple conditions and isin
  • Cleans data: isna, fillna, dropna, drop_duplicates, astype, to_datetime — and remembers df = ...
  • Uses groupby + sum/mean/count/agg
  • Draws line, bar, histogram, scatter with titles and labels, and savefig
  • Has used np.polyfit to train a small model and explains why predicting outside the data is dangerous
  • Homework is on GitHub and the notebook outputs are visible