Programming with Python
Kühne Logistics University Hamburg - Fall 2026
Sessions VI-VII. The first 40 minutes are the checkpoint. It starts now.
random and seeds, NumPy arrays and masksCmd/Ctrl+S, then menu → Download → Download Python code → upload the .py to the “Checkpoint 4” assignment on Moodle, before the 40 minutes are up. No retakes: one session.
No live checks this time. Nothing in the notebook tells you whether you’re right: checking is the job. The grading runs on my side. Everything in it was rehearsed in the labs.
This morning rival chain MunchCorp bragged in a press release about their “data-driven growth.” The evidence attached: exactly one pie chart. The investor slid it across the table: “Yours will be better. Show me the data.”
Tobi didn’t wait. He’d pasted the question into a chatbot, proudly turned the laptop around, and hit run:
AttributeError: 'DataFrame' object has no attribute 'overview'
The gAI sounded certain, but the method it called does not exist. Today starts with the tool that answers the investor for real, pandas, and then comes back to that difference.
Last session, a NumPy array turned a thousand numbers into one fast object, but every value had to share one type, and columns had no names.
The convention everyone uses: import pandas as pd.
Keys become column names; each list becomes a column; one order per row, mixed types together:
order_id zone items total_eur
0 101 Nord 2 18.5
1 102 Sued 1 7.2
2 103 Nord 3 24.0
3 104 Hafen 1 6.8
4 105 Sued 4 31.4
Nobody types two weeks of orders by hand: today’s lab loads a real CSV (a table saved as plain text, one row per line) in one line, orders = pd.read_csv("orders_messy.csv"). The file sits in the same folder as your script, so its name is all the path you need.
.head() and .info()Glance at a table first. .head() shows the top rows; .info() is your first sanity check: right number of rows? Any surprising type?
order_id zone items total_eur
0 101 Nord 2 18.5
1 102 Sued 1 7.2
2 103 Nord 3 24.0
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 order_id 5 non-null int64
1 zone 5 non-null object
2 items 5 non-null int64
3 total_eur 5 non-null float64
dtypes: float64(1), int64(2), object(1)
memory usage: 292.0+ bytes
.describe().describe() computes count, mean, min, max and quartiles for every numeric column at once:
order_id items total_eur
count 5.000000 5.00000 5.000000
mean 103.000000 2.20000 17.580000
std 1.581139 1.30384 10.688873
min 101.000000 1.00000 6.800000
25% 102.000000 1.00000 7.200000
50% 103.000000 2.00000 18.500000
75% 104.000000 3.00000 24.000000
max 105.000000 4.00000 31.400000
One call, the whole summary: this is the .overview() Tobi’s chatbot wished existed.
Pick one column by name:
0 Nord
1 Sued
2 Nord
3 Hafen
4 Sued
Name: zone, dtype: object
Keep rows with a boolean mask, exactly the NumPy idea from last session:
order_id zone items total_eur
0 101 Nord 2 18.5
2 103 Nord 3 24.0
df["zone"] == "Nord" builds a column of True/False; indexing with it keeps the True rows. .loc exists for label-based selection, but a plain mask covers today.
Tobi wants the orders over 20 EUR. He compares the column and prints the result:
a) a column of True/False, one per row
b) only the rows whose total is over 20
c) a single True, since some order is over 20
0 False
1 False
2 True
3 False
4 True
Name: total_eur, dtype: bool
order_id zone items total_eur
2 103 Nord 3 24.0
4 105 Sued 4 31.4
a) a column of True/False. The comparison alone is the mask, a Series with one boolean per row. The rows only appear once you index with it.
Scan the QR or type the link:
python.tobiasvlcek.com/notebooks/ex_08_a/
Any questions? Let me know!
Compute a new column from existing ones, then sort. Work on a copy so the original stays intact:
order_id zone items total_eur eur_per_item
4 105 Sued 4 31.4 7.85
2 103 Nord 3 24.0 8.00
0 101 Nord 2 18.5 9.25
1 102 Sued 1 7.2 7.20
3 104 Hafen 1 6.8 6.80
sort_values reorders rows by a column, biggest orders first here. The new column is just arithmetic on two others, computed for every row at once.
Tobi filters for the Nord zone, but types it lowercase. The data spells it "Nord". What does this print?
a) an empty table, no error
b) the Nord rows anyway: case is ignored
c) a KeyError for the missing zone
Empty DataFrame
Columns: [order_id, zone, items, total_eur]
Index: []
rows: 0
a) an empty table. "nord" matches nothing, so the mask is all False and pandas hands back zero rows. No error…
This is the dangerous kind of bug: it runs, it just returns nothing. pandas won’t warn you, which is exactly why we try to test filters on a case whose answer we know.
groupbyThe investor’s real question is per zone: which area brings in the most?
groupby splits the table by a category, then computes once per groupdf.groupby("zone")["total_eur"].sum() → one total for each zoneThat’s the teaser: the lab needs more of groupby than this slide shows. It is a good one to ask your agent about. How to check what it hands you comes next.
Scan the QR or type the link:
python.tobiasvlcek.com/notebooks/ex_08_b/
Any questions? Let me know!
Two things turn a vague request into a useful answer:
orders with columns zone (text) and total_eur (float).”total_eur for zone Nord, as a single number.”“DataFrame
orders, text columnzone, float columntotal_eur. Write one line that returns the totaltotal_eurwherezoneisNord.”
Vague in, vague out. Specific in, checkable out, then you iterate.
Tobi’s mistake wasn’t using gAI. It was shipping without checking. Every line a gAI hands you should get three passes if it is critical:
Five orders with values you can add in your head, and the gAI’s suggested line for the Nord average:
20.0
The Nord orders are 10, 20 and 30. You can average those in your head. Does the gAI’s answer match? Then the line has earned trust on the real file. With gAI, verification is often the job now, not typing the code yourself.
Tobi asks the gAI for the Nord average on the same five orders. It hands back one line and calls it done. What does this print?
a) 20.0
b) an AttributeError
c) 14.5
14.5
20.0
c) 14.5: the line averages all five orders. Nobody filtered for Nord, and pandas has no way to know that was the question.
No traceback, a plausible number, and still wrong. “Code runs” only clears step 2. Step 3 is where the actual number get tested.
.summarize() exist?The gAI wrote this for Tobi. orders is a tiny two-row frame. What happens on the last line?
a) prints a summary table
b) raises an AttributeError
c) returns an empty DataFrame
AttributeError: 'DataFrame' object has no attribute 'summarize'
b) AttributeError: pandas has no .summarize(). The gAI invented it.
.describe() total_eur
count 2.000000
mean 10.750000
std 1.767767
min 9.500000
25% 10.125000
50% 10.750000
75% 11.375000
max 12.000000
A gAI that sounds sure is not the same as a function or method that exists.
Session VI gave you the basics, so here is why it sometimes fails.
.summarize is a plausible pandas name: short, lowercase, shaped like .describe().Fluent and wrong is difficult to fix. It is how gAI works due to its stochastic nature.
Every stage rewards sounding right. Verifacation is your job (at least for now), and step 3 of the verify workflow is how.
In your project (Part III) you will have to list each AI use in one line. Labs and checkpoints need no note. This is a good habit to get into for your own work. . . .
Although it sounds strange, it protects you: it separates the work you understand from the work you pasted, so when a reviewer (or the investor or a different professor) asks “how does this work?”, you’re never caught claiming something you can’t explain.
Sometimes the fastest path is a different one:
Use gAI to draft the unfamiliar and to explain the confusing, not to skip thinking you can do yourself (at least while you are in university).
Scan the QR or type the link:
python.tobiasvlcek.com/notebooks/ex_08_c/
Any questions? Let me know!
lab_08_dataroom.py and orders_messy.csv from Episode 8: The Data Room and move them into python-labsuv run python lab_08_dataroom.py. It checks every task and stops at the first one without a valid answerShow me the last block before you leave, for feedback: the script prints SHOW THIS IN CLASS, and I compare your numbers with mine.
Open the agent panel (the sparkle icon, bottom right), pick Mistral Vibe, and ask for one task at a time:
In
lab_08_dataroom.py, fill inrevenue_by_zonein Task 4: a dict, zone name to euros, from the DataFrameclean. Change nothing else.
More on how gAI works and how you can use it even better in Session X. Today it is a tool you supervise.
pd.DataFrame from a dict, pd.read_csv from a file; .head(), .info(), .describe() to look before you leap.df["col"], a boolean mask (df[df["zone"] == "Nord"], case-sensitive!), a new column on a .copy(), groupby per category. pandas won’t warn; you verify.Next episode: charts the investor can’t argue with. Numbers convince the careful and a good plot convinces the room. We turn the data room into pictures.
Working with AI this session? Revisit the AI Tools page: context-and-constraints prompting, the verify workflow, and the disclosure rule for your project. For pandas itself, the official 10 minutes to pandas guide is the friendliest next step.
For more, see the literature list of this course.
Lecture VIII - Pandas and gAI | Dr. Tobias Vlćek | Home