Programming with Python
Kühne Logistics University Hamburg - Fall 2026
Friday, the boardroom. Picture it: the investor slides a pencil out of her jacket and sharpens it while you set up, the sound louder than it has any right to be.
Last week you handed her a table she could trust. She read exactly one row, looked up, and said the thing this whole session is about:
“Tables are homework. Charts are arguments.”
A number convinces the careful. A picture convinces the room. Today: draw the right chart for the question, and refuse to draw a misleading one.
Three questions from Episode 8. Commit. Hands up before the reveal.
returns…
a) a True/False column b) only the Nord rows c) an error from comparing text
b) only the Nord rows: the True/False column is what the inner expression orders["zone"] == "Nord" makes. Wrapped in orders[...], that mask keeps the rows where it’s True and drops the rest.
Tobi’s AI wrote this. orders is a normal DataFrame. What happens?
a) a summary table b) an empty DataFrame c) an AttributeError
c) AttributeError: pandas has .describe(), not .summarize(). The AI invented a plausible name. An AI that sounds sure is not the same as an API that exists. You verify, every time.
shows…
a) the first five rows of the column, like .head() b) count / mean / std / min / quartiles / max c) the column’s dtype, like .dtypes
b) count / mean / std / min / quartiles / max. One line, the column’s whole statistical fingerprint. That’s the real method Tobi’s AI was reaching for.
Everyone plots with matplotlib, imported as plt. Four lines turn a list of numbers into a picture:
Two habits for every chart cell: open with plt.figure() (a clean canvas) and end with plt.gca(). In marimo there’s no plt.show(); the figure appears as the cell’s last expression, and plt.plot(...) alone returns line objects, not a picture. (A plain script run from a terminal does need plt.show() at the end; that’s Session X.) Same frame around every chart in today’s lab.

Tobi is in a hurry and hands plot one list, no days:
What does the x-axis show?
a) 3, 5, 4, the values themselves b) 0, 1, 2, the list positions c) 1, 2, 3, counted the human way
Predict first. Pick a letter, then I reveal the answer.
b) 0, 1, 2: with one list, matplotlib takes it as y and invents x as the positions, and Python counts positions from 0:
Want days 1-5 on the axis? Pass them: plt.plot(days, revenue), x first.

Every chart names its axes. xlabel, ylabel, title, and (when there’s more than one line) a legend:
import matplotlib.pyplot as plt
days = [1, 2, 3, 4, 5]
revenue = [120, 90, 140, 160, 130]
plt.figure()
plt.plot(days, revenue, label="Revenue") # label feeds the legend
plt.xlabel("Day") # what the x-axis counts
plt.ylabel("Revenue (€)") # what the y-axis measures
plt.title("This week's revenue") # what the picture is about
plt.legend() # the little key, one entry per label
plt.gca()
Scan the QR or type the link:
python.tobiasvlcek.com/notebooks/ex_09_a/
First predict what happens, then run it.
Two keywords cover almost everything: color and linestyle. Restraint is the professional move. One clear line beats a rainbow:

Tobi runs two plot calls, back to back, with no plt.figure() between them:
a) one chart with two lines b) two separate charts, one per call c) an error: the figure is already in use
Predict first. Pick a letter, then I reveal the answer.
a) one chart with two lines, because matplotlib keeps drawing on the current figure until you start a new one with plt.figure():
That’s exactly why every chart cell opens with plt.figure(): it’s how you say “new picture, start clean.”

Scan the QR or type the link:
python.tobiasvlcek.com/notebooks/ex_09_b/
First predict what happens, then run it.
The wrong chart is its own kind of lie. Start from the question, not the chart:
| The question | The chart | The call |
|---|---|---|
| Compare categories? | bar | plt.bar(labels, heights) |
| See a distribution? | histogram | plt.hist(values) |
| Two numbers per order? | scatter | plt.scatter(x, y) |
| Change over time? | line | plt.plot(x, y) |
Line you already own from block one. Missing on purpose: the pie chart. The eye can’t compare slice sizes; a bar is clearer.
“Which dish sells best?” Categories to compare, so that’s a bar:
The tallest bar answers the question at a glance: Pizza.

“How are our delivery times spread out?” One column of numbers dropped into buckets. That’s a histogram:
Most deliveries land in the mid-20s, with one lonely slow one far right. A histogram shows shape, not individual values.

“Do bigger orders take longer?” Two numbers per order, one on each axis, which makes it a scatter:
Just a cloud. No upward drift. Sometimes the answer is “there’s no pattern here.”

Tobi types plot where he meant scatter. Five orders, in the order they came in:
a) a zig-zag line hopping between the points in list order b) the same dots as scatter, joined into a smooth trend c) an error: plot refuses x values that are not sorted ascending
Predict first. Pick a letter, then I reveal the answer.
a) a zig-zag: plot connects the points in the order you gave them, never mind the x values. Order 1 to order 2 to order 3, back and forth:
A line says the points follow each other, and orders don’t. Two numbers per order with no sequence between them is a job for scatter.

The same four numbers, two ways. On the left, a bar per number; on the right, a histogram:
The bar chart shows 4 bars. How many bars does the histogram show?
a) 4: one bar per number b) 15: one bar per unit up to the max c) about 2-3 lumps: it counts ranges
Predict first. Pick a letter, then I reveal the answer.
c) about 2-3 lumps: the histogram groups the numbers into ranges and counts how many fall in each. 8 and 9 land in the same bucket, so that bar is two tall:
Bar counts CATEGORIES; histogram counts RANGES. Same numbers, different question.

Scan the QR or type the link:
python.tobiasvlcek.com/notebooks/ex_09_c/
First predict what happens, then run it.
A five-minute pause after this one, then the axis and the AI chart assistant.
Four weeks of revenue, [96, 98, 97, 99], barely moving. Where the y-axis starts decides: the left gets you a term sheet, the right gets you trusted. Same numbers, and the room sees a rocket or the truth:
import matplotlib.pyplot as plt
weeks = [1, 2, 3, 4]
revenue = [96, 98, 97, 99]
plt.figure()
plt.subplot(1, 2, 1)
plt.plot(weeks, revenue, marker="o")
plt.ylim(96, 99) # tight: DRAMA
plt.title("Rocket (ylim 96-99)")
plt.subplot(1, 2, 2)
plt.plot(weeks, revenue, marker="o")
plt.ylim(0, 100) # from zero
plt.title("From 0")
plt.gca()
Revenue [710, 718, 724, 731], up 3 % over four weeks. Tobi’s AI drafts the chart and adds one line:
What does the room see?
a) a flat line: 3 % stays 3 % on any axis b) an error: ylim has to include zero on a line chart c) a line climbing almost the whole chart height
Predict first. Pick a letter, then I reveal the answer.
c) a climb across the chart: the axis spans 35 units and the line rises 21 of them, 60 % of the picture for 3 % of growth:
Python raises no error and no warning here. The code is fine; the axis is what misleads.

She’s the type who reads Formular 27b/6 for fun. A stretched axis is the first thing she catches, and the last thing you want to explain.
Scan the QR or type the link:
python.tobiasvlcek.com/notebooks/ex_09_d/
First predict what happens, then run it.
AI is good at plotting code, if you check the result:
orders with columns zone (text) and total_eur (float). Bar chart of total revenue per zone.”AI drafts. You decide what goes on the slide.
orders["revenue"], but your column is total_eur: a KeyError, exactly the confident-nonsense you caught in Episode 8. Read the code before you run it.Both threads, one rule: an AI chart runs long before it’s true. You verify the columns and the axis, every time.
Scan the QR or type the link:
python.tobiasvlcek.com/notebooks/ex_09_e/
First predict what happens, then run it.
The finale leaves the browser for your own machine. Four things before you arrive, in this order, about 45 minutes:
gh): install, log in once. Git BasicsDo it in advance, and bring the laptop plus one downloaded lab .py. Session X opens with Checkpoint 5 (Sessions VIII-IX), then builds on a working toolchain. Install fights you? Ask in class or by e-mail.
plt.figure() and closes with plt.gca()Download your .py before you leave. It’s the appendix of your pitch: the file that proves every chart came from the real data.
plt.figure(), close with plt.gca(); label the axes and title it. A chart nobody can read argues nothing.Season finale next: git, real files, and the project kickoff. We leave the browser for your own machine, so install the toolchain this week. Session X needs it from the first minute.
Building charts with AI this session? Revisit the AI Tools page: describe-then-verify is the whole workflow. Matplotlib’s own pyplot tutorial is a friendly next step.
For more, see the literature list of this course.
Lecture IX - Data Visualization | Dr. Tobias Vlćek | Home