1 · Python fundamentals

Not a tour of the language — only what an analyst actually uses, and each idea explained by why it exists, not just how it is typed.

1.1 · Types: a promise about a value

A type tells you what you may do with a value. Numbers add up, strings glue together. Mix them up and "120" + "80" silently becomes "12080" instead of 200 — a bug that raises no error and simply puts a wrong number in your report.

None deserves special care: it means "there is no value", which is not zero. A user who never bought has revenue None; a user who bought and refunded everything has 0. Treating them the same shifts your average.

1.2 · Structures: list, dict, set

The structure you choose decides both readability and speed. The rule: list when order matters, dict when you look values up by key, set when you need uniqueness or a fast "is it there?" check.

The speed gap is not academic. A list checks elements one by one; a set jumps straight to the answer through a hash. Inside a loop over a million rows that is the difference between seconds and hours.

Practical rule: if you test membership inside a loop, convert the list to a set first. One line, and a slow script stops being slow.
1.3 · Comprehensions

A comprehension builds a collection in one line. The gain is not brevity but intent: it says "this is a transformation of that", instead of "make an empty list, then append over and over".

1.4 · Functions

A function is how you stop copy-pasting. The real benefit is not elegance: when a formula changes you fix it once instead of hunting every copy — and a copy you miss is exactly how two slides end up disagreeing.

Rule of thumb: a calculation that appears twice is a function waiting to happen. Add a docstring — that is the text your editor shows as a hint later.
2 · Core algorithms

These are the classic interview problems — but what matters here is the pattern behind each one. The same four patterns underpin how pandas performs a join, a group-by or a rolling window.

2.1 · Complexity in one paragraph

Complexity answers one question: as data grows, how fast does the work grow? O(n) means one pass — double the rows, double the time. O(n²) means comparing every element with every other — double the rows and the time goes up fourfold. Most "why is my script hanging" stories are an accidental O(n²).

The practical takeaway: a loop inside a loop over the same data is a red flag. Almost always it can be replaced by a dictionary lookup, turning O(n²) into O(n).

2.2 · Pattern: a dict as memory

The single most useful pattern in analytics. Instead of comparing every pair, you walk once and remember what you have already seen. Whenever a task sounds like "find a pair / find a duplicate / match one table to another", this is the shape of the answer.

Why this matters beyond interviews: a merge in pandas is exactly this — it builds a hash of one table and walks the other once. Understanding the pattern is understanding why a join on an indexed key is fast.
2.3 · Pattern: counting and top-K

Counting occurrences is the most common analytical operation there is: popular pages, frequent errors, best-selling items. Counter does it in one pass and hands you the ranking ready-made.

2.4 · Pattern: sliding window

When a question involves "k consecutive days", recomputing the sum for every position is wasteful. A sliding window adds the entering value and subtracts the leaving one — one pass instead of many. This is precisely what rolling() does in pandas.

2.5 · Pattern: grouping by a computed key

Sometimes the key you need is not in the data — you compute it. Sorting the letters of a word groups anagrams; normalising a name groups duplicate customers; rounding a timestamp groups events into hours. The mechanism is identical, and it is what groupby does under the hood.

What ties these together: four of the five problems above are solved by "remember what you have seen in a dict or set". That single idea converts brute-force comparison into a single pass — and it is the foundation of every fast operation in pandas.
3 · Working with data

Where the language stops and the craft begins. Half of all analytical errors are born not in the calculation but in how the table was assembled.

3.1 · numpy: why vectorisation

numpy stores numbers in one continuous block of memory and applies an operation to the whole block at once, in compiled code. A Python loop, by contrast, unpacks and repacks every value individually. That is where the order-of-magnitude difference comes from — it is not a style preference.

Rule: if you are writing for over the rows of a DataFrame, there is almost certainly a vector equivalent. The exact multiple depends on the machine, but the order of magnitude is stable.
3.2 · Building a dataset properly

The most common mistake is leaving everything as strings. Then dates will not sort, categories waste memory, and numbers will not add up. Declare types as you build the table, not after something breaks.

Note the fixed seed. Anything random — sampling, simulation, synthetic data — must be reproducible, otherwise tomorrow you get different numbers and cannot tell whether the data changed or you simply got lucky.

Why category matters: on 500 rows the saving is invisible, on ten million it is the difference between "the table fits in memory" and "the kernel died". It also gives correct sorting: ordered=True knows that free comes before pro.
3.3 · Long and wide format

This is the central idea of data organisation. Wide is comfortable for a human reading a report. Long (or "tidy") is the only shape that group-bys and plotting libraries work with naturally.

The tidy rule: one row is one observation, one column is one variable. If column names contain values (jan, feb, mar are values of the variable "month"), the table is wide and should be reshaped with melt before analysis.
Spot the detail in the output: after pivot the columns came back alphabetically — feb, jan, mar. For months that is wrong. This is exactly why a month should be stored as a date or an ordered category, never as a plain string.
3.4 · pandas: select, group, join

Four operations cover most day-to-day work: look at the data, select what you need, group it, and join it to another table. Everything else is a variation.

The distinction people get wrong: .loc addresses by label, .iloc by position. And count() counts rows while nunique() counts distinct values — confusing those two is the most common cause of inflated metrics.
3.5 · Checking and cleaning

This is the step that separates an analyst from someone who can write code. A beautiful chart built on broken data is worse than no chart at all, because people will believe it.

The ritual after every load: shape, missing values, duplicates, types, and value ranges. Five lines that catch most problems before they reach a presentation.
A subtlety visible in the output: duplicated() found zero full duplicates even though user 2 appears twice — the rows differ in the revenue column. That is why duplicate users are caught with drop_duplicates(subset=[...]), not by comparing whole rows.
And note "XX" — a country that does not exist, which cleaning did not remove: the code fixed the case but never checked against a reference list. Such things are only caught by looking at value_counts(). Automation helps; it does not replace looking at your data.
3.6 · Writing code you can trust

Analytical code is read more often than it is written, and decisions are made from its output. Badly written code is not merely ugly — it quietly produces wrong numbers.

Names instead of riddles. d, x, tmp2 mean nothing a week later. orders, paid_orders, arpu_by_country read like a sentence. A variable name is free documentation.

An explicit copy instead of a warning. The most common pandas trap: you filter a table, then write into the result, and pandas cannot tell whether you meant to change the copy or the original. The result is a warning and values that silently do not get written.

One readable chain instead of five variables. With t1…t5 it is easy to lose track of which one is current and accidentally carry a stale table into the final calculation.

The underlying point: code is written once and read dozens of times. If you have to decode your own script, that is not saved time — it is a deferred error.
4 · Business cases

Six tasks in the form they actually arrive: a question from a product manager, a trap in the naive answer, and the code that gets it right.

Case 1 · Signup funnel
Task: "Signups are low. Which step do people drop at?"

The thing to get right: count distinct users, not events. Otherwise one person who opened the form twice is counted twice and the funnel inflates.

2026-08-28T14:48:04.667376 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
Answer for the product manager: the absolute losses are equal (two people per step), but in relative terms the last transition hurts most — only half of those who submit ever activate. Fix the onboarding, not the form.
Case 2 · "ARPU is up" — is it?
Task: "Average revenue per user went from 22 to 24.7. Time to celebrate?"

Revenue is almost always skewed: a handful of large customers drag the mean upwards. One number hides that; the distribution reveals it.

2026-08-28T14:48:17.030286 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
Conclusion: 72% of users earn less than the "average", and the top 10% bring 45% of all revenue. The ARPU rise could come from a couple of large deals while the typical user did not change at all. Track the median and segment.
Case 3 · Cohorts
Task: "We spent a quarter improving onboarding. Did it work?" Overall retention will not answer — it mixes old and new users.

The technique: pivot turns a long table into a cohort matrix, then each row is divided by its own month zero.

2026-08-28T14:48:30.508149 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
How to read it: look down column 1 — 30.2 → 32.4 → 34.6. Each later cohort retains better at the same age. That is the proof the onboarding worked, and overall retention would never have shown it.
Case 4 · A/B test
Task: "Variant A shows 11.36% against the control's 9.80%. Ship it?"

A point estimate alone means nothing — you need the confidence interval. If the intervals overlap, the difference may well be noise.

2026-08-28T15:05:07.917241 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
Conclusion: p = 0.073 > 0.05 and variant A's interval overlaps the control. The difference is not significant — shipping now would be premature. This is exactly why error bars are more honest than bare bars.
Case 5 · RFM segmentation
Task: "We have 800 customers and one campaign budget. Who do we write to?"

RFM splits the base along three axes: Recency, Frequency, Monetary. Each axis is cut into quintiles with qcut, and the scores combine into segments.

2026-08-28T15:05:29.183523 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
Answer for marketing: "At risk" is 111 people who used to buy often and then disappeared. They are the most profitable target for reactivation — they have already proved they will pay. The 138 "dormant" are cheaper to let go.
Case 6 · Finding anomalies
Task: "Something broke in early July but the chart looks normal. Check it."

The technique: smooth the series with a rolling mean, subtract it from the actual values, and flag points where the deviation exceeds a few standard deviations.

2026-08-28T15:05:29.397709 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
Important detail: the method also flagged upward spikes — those are the rebound after the dip, not a failure. An automatic detector finds the unusual; a human always interprets it. Never wire this straight into alerts without that step.
5 · Advanced visualisation

Three techniques that separate an analyst's chart from a spreadsheet default. Each solves a specific perception problem.

Small multiples instead of spaghetti

Beyond three series, lines on a single chart become unreadable. The fix is to split into small panels and highlight one segment per panel, keeping the others as grey context.

2026-08-28T15:05:56.142002 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
Why it works: the grey background provides comparison — you see immediately that Tablet falls fastest while Desktop holds. On one combined chart that would drown in crossings.
Show the distribution, not just the mean

Two bars of averages hide everything interesting. A box plus the raw points plus a mean marker shows shape, spread and outliers at once.

2026-08-28T15:05:56.319286 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
Look closely: the red diamond (mean) sits clearly to the right of the box centre (median) — that is skewness made visible. A bar chart would never show it.
Waterfall: decomposing a change

The question "revenue grew by 73k — where from?" is best answered by a waterfall: every contribution is separate, and gains are distinguished from losses by colour.

2026-08-28T15:06:35.880591 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/
Produced by the code above
Implementation subtlety: for negative values the bar's base is cum + v and its height is abs(v). Passing a negative height together with a shifted base draws the bar at half its size — a classic bug when building a waterfall by hand.
Rules for a good chart
The title is the conclusion, not the label"Variants overlap the control — no winner" beats "Conversion by group". The reader gets the thought, not the raw data.
Remove the frame and surplus gridlinesAnything that carries no information steals attention from what does.
Label values directly on the chartIt saves the reader from looking back and forth to the axis.
Do not truncate the Y axis on bar chartsBars are compared by length, so a cut axis exaggerates the difference. For lines it is acceptable.
Colour is meaning, not decorationOne accent for the point being made, grey for context. A rainbow without meaning slows reading down.
Show uncertaintyConfidence intervals are more honest than point estimates and prevent premature conclusions.
The test before you send it: cover the title and show the chart to a colleague. If they cannot tell you what point you were making, the chart is not finished.
6 · Common mistakes
MistakeWhat it costs youDo this instead
count() instead of nunique()The funnel inflates and conversion is overstatedCount distinct users
Reporting only the meanA few large customers mask a decline in everyone elseMedian, percentiles, histogram
Overall retention instead of cohortsMixes old and new users, so improvements stay invisibleA cohort matrix
A loop instead of vectorisationSlow, longer to write, more room for errorOperate on the whole column
Ignoring SettingWithCopyWarningValues silently fail to be written.copy() or .loc[...]
Never checking isna()mean() quietly skips gaps and the metric driftsCount missing values before calculating
Not fixing the random seedResults change between runs and cannot be reproduceddefault_rng(42)
Comparing groups of different size by absolute numbersThe bigger segment always appears to winCompare shares, normalise
← A/B Testing Calculator Causal Inference Home