SQL
Warehouse filtering, joins, aggregation and reproducible governed metrics.
Use Python where it gives you analytical leverage. Do not rewrite a perfectly good warehouse query in pandas just because Python looks more impressive.
Python is not a badge of seniority. It is useful when it makes the analysis easier to reason about, reproduce or extend.
“We have the clean order table. Now we need to inspect distributions, compare dozens of segments, test different thresholds and document the exploration.”
You could force all of this into one giant SQL script. But once the task becomes iterative exploration rather than stable warehouse aggregation, Python can be a better analytical workspace.
Warehouse filtering, joins, aggregation and reproducible governed metrics.
Flexible cleaning, exploratory analysis, statistics and reusable analytical workflows.
Fast checks, small reconciliations and stakeholder-friendly ad hoc work.
Repeatable monitoring, interactive slicing and decision-facing communication.
A strong analyst is not the person who uses the most advanced tool. It is the person who chooses the lowest-complexity workflow that stays trustworthy.
Read a clean extract or query result into a DataFrame.
Check shape, dtypes, missing values, duplicates and basic ranges.
Create explicit derived columns without hiding business logic.
Group, compare, rank and inspect distributions or anomalies.
Reconcile important totals back to the source query.
Export only the tables or visuals needed for the decision.
import pandas as pd
orders = pd.read_csv("orders.csv", parse_dates=["created_at", "completed_at"])
print(orders.shape)
print(orders.dtypes)
print(orders.isna().mean().sort_values(ascending=False).head(10))
print(orders["order_id"].duplicated().sum())
print(orders["status"].value_counts(dropna=False))
print(orders["gross_value"].describe()) This is not glamorous, but it gives you immediate information about scale, types, missingness, uniqueness, categories and suspicious numeric ranges.
orders["delivery_minutes"] = (
orders["completed_at"] - orders["created_at"]
).dt.total_seconds() / 60
orders["is_late"] = orders["delivery_minutes"] > 45
orders["order_value_band"] = pd.cut(
orders["gross_value"],
bins=[0, 15, 30, 60, float("inf")],
labels=["0–15", "15–30", "30–60", "60+"]
) Before using is_late, confirm that 45 minutes is the agreed SLA and that completed_at is the correct endpoint for the business definition.
segment_summary = (
orders
.groupby(["city", "customer_type"], dropna=False)
.agg(
orders=("order_id", "nunique"),
median_delivery=("delivery_minutes", "median"),
p90_delivery=("delivery_minutes", lambda s: s.quantile(0.90)),
late_rate=("is_late", "mean")
)
.reset_index()
.sort_values(["late_rate", "orders"], ascending=[False, False])
)
print(segment_summary.head(20)) Notice the analysis uses median and p90 alongside late rate. That connects directly to the previous statistics module: the tail of the distribution may matter more than the average.
orders = orders.merge(
cities[["city_id", "city_name", "country"]],
on="city_id",
how="left",
validate="many_to_one"
) Distinct order rows.
Expected if city_id is unique.
Make pandas fail loudly when the relationship is not what you expected.
Business question, metric definitions and data source.
Shape, types, nulls, keys, freshness and controls.
Explicit, documented analytical columns.
Segments, distributions, rankings and hypotheses.
Reconcile important numbers to the source.
Decision-ready tables, visuals and conclusions.
I have an order-level pandas DataFrame called orders.
Known grain:
one row per order
Important columns:
order_id, customer_id, city_id, created_at, completed_at,
status, gross_value, customer_type
Task:
Investigate what segments are associated with delivery times above the 45-minute SLA.
Before writing code:
1. Restate the grain and SLA definition.
2. List the QA checks you would run first.
3. Identify useful distribution statistics beyond the mean.
4. Write readable pandas code in small steps.
5. Add validation checks after any merge.
6. Do not invent columns or conclusions that are not in the data. Can this task be done more simply and safely in SQL?
Did I inspect shape, data types, nulls and duplicates before analysis?
Are derived columns based on explicit business definitions?
Did a merge change the expected number of business entities?
Can I reproduce important totals with a control query?
Did I avoid silently dropping nulls or outliers?
Is the notebook readable from top to bottom without hidden state?
Could someone else rerun this analysis and get the same result?
Prove the data deserves trust.
Make the important comparison easy to see.
Calibrate uncertainty and avoid weak inference.
Use Python when flexible analysis adds leverage.
The analyst workflow is now complete from trustworthy data to decision-ready evidence. Next, the course changes gear: instead of treating AI as a helper inside individual lessons, we will learn how to make AI part of the analytical system itself.