Python for data: NumPy and pandas
Learn the two libraries behind Python data work. Load a CSV, find and fix missing values, filter and sort rows, join tables and summarise sales with groupby, using a small coffee shop orders file.
You will learn the two libraries behind almost all Python data work: NumPy (fast arrays of numbers) and pandas (tables, called DataFrames). By the end you will load a CSV, find missing values, filter rows, join two tables and summarise sales by item and by day.
Prerequisites: basic Python (variables, lists, functions) and a notebook from the previous lesson. Every code block runs in Colab or Kaggle as it is. We use a small Brew & Bean orders file written inside the code, so you need no download.
NumPy in five minutes
A NumPy array is like a Python list of numbers, but it does maths on the whole list at once. This is called vectorised maths, and it is much faster and shorter than writing a loop.
import numpy as np
prices = np.array([120, 180, 220, 80, 90])
print(prices.mean(), prices.max()) # 138.0 220
print(prices * 2) # [240 360 440 160 180]
print(prices[prices > 100]) # [120 180 220] (a boolean mask)
m = np.arange(6).reshape(2, 3) # 2 rows, 3 columns
print(m.shape) # (2, 3)
print(m.sum(axis=0)) # down the columns: [3 5 7]
print(m.sum(axis=1)) # across the rows: [ 3 12]prices > 100 creates True/False for each element. Using it inside the brackets keeps only the True ones. This mask idea is the same one pandas uses for filtering.
Why NumPy
| Idea | Plain Python list | NumPy array |
|---|---|---|
| Multiply all by 2 | needs a loop or comprehension | a * 2 |
| Average | sum(a) / len(a) | a.mean() |
| Filter | comprehension with if | a[a > 100] |
| Speed on a million numbers | slow | fast |
Create a DataFrame from a CSV
A DataFrame is a table: rows are records, columns are variables. A Series is one column. Real data usually arrives as a CSV file, read with pd.read_csv("file.csv"). To keep this lesson self-contained we read the CSV text from a string.
import pandas as pd
from io import StringIO
csv = """order_id,date,item_id,qty,city
1,2026-10-01,espresso,2,Bengaluru
2,2026-10-01,cappuccino,1,Bengaluru
3,2026-10-02,cold-brew,3,
4,2026-10-02,croissant,2,Bengaluru
5,2026-10-03,espresso,,Bengaluru
6,2026-10-03,masala-chai,4,Bengaluru
7,2026-10-04,cappuccino,2,Bengaluru
8,2026-10-04,espresso,1,Bengaluru
"""
orders = pd.read_csv(StringIO(csv), parse_dates=["date"])
print(orders.head(3))
orders.info()parse_dates turns the date column into real dates instead of text. With your own file, use pd.read_csv("orders.csv", parse_dates=["date"]).
head() shows the first rows. info() lists each column, its type and how many values are not missing. You should see 8 rows, and qty and city should each show 7 non-null values. That tells you each has one missing value. (Exact type names such as object or str depend on your pandas version.)
print(orders.describe()) # count, mean, min, max... for number columns
print(orders.isna().sum()) # missing values per columndescribe() gives a quick feel for the numbers. isna().sum() counts missing values in each column: here 1 for qty and 1 for city.
Clean: handle missing values
You have three choices for missing data: drop the rows, fill them with a sensible value, or leave them and use a method that copes. The right choice depends on why the value is missing. For a missing quantity, the median is a safe fill. For a missing city, a label like "Unknown" keeps the row.
orders["qty"] = orders["qty"].fillna(orders["qty"].median())
orders["city"] = orders["city"].fillna("Unknown")
print(orders.isna().sum().sum()) # 0The median of the known quantities is 2, so order 5 now has qty 2. The last line prints 0 because no missing values remain.
Do not fill without thinking
Filling values hides a problem if you do it blindly. Ask why the value is missing (a billing bug? a form field that is optional?), and record what you did. In a real project you would also tell the owner that one order had no quantity.
Select, filter and sort
print(orders["qty"]) # one column (a Series)
print(orders[["order_id", "item_id"]]) # several columns: note the double brackets
print(orders[orders["qty"] >= 3]) # rows where qty is at least 3
print(orders[(orders["item_id"] == "espresso") & (orders["qty"] > 1)])
print(orders.sort_values("qty", ascending=False).head(3))When you combine conditions, wrap each in parentheses and use & for and, | for or. Writing and / or here causes an error.
Join two tables
Orders only contain item_id. Prices live in the menu table. A merge joins them on the shared column, like a SQL JOIN.
menu = pd.DataFrame({
"item_id": ["espresso", "cappuccino", "cold-brew", "filter-coffee",
"masala-chai", "croissant", "banana-bread", "veg-sandwich"],
"name": ["Espresso", "Cappuccino", "Cold Brew", "Filter Coffee",
"Masala Chai", "Butter Croissant", "Banana Bread", "Veg Grilled Sandwich"],
"category": ["coffee", "coffee", "coffee", "coffee", "tea", "bakery", "bakery", "food"],
"price": [120, 180, 220, 80, 90, 150, 130, 160],
})
df = orders.merge(menu, on="item_id", how="left")
df["revenue"] = df["qty"] * df["price"]
print(df[["order_id", "name", "qty", "revenue"]])how="left" keeps every order even if its item is not found in the menu. A new column revenue is created by multiplying two columns, row by row, with no loop.
order_id name qty revenue
0 1 Espresso 2.0 240.0
1 2 Cappuccino 1.0 180.0
2 3 Cold Brew 3.0 660.0
3 4 Butter Croissant 2.0 300.0
4 5 Espresso 2.0 240.0
5 6 Masala Chai 4.0 360.0
6 7 Cappuccino 2.0 360.0
7 8 Espresso 1.0 120.0The qty column shows 2.0 rather than 2 because a column with a missing value was stored as decimals.
Group and summarise
groupby is the most useful pandas idea. It splits the table by a column, applies a calculation to each part and combines the answers: split, apply, combine.
by_item = df.groupby("name")["revenue"].sum().sort_values(ascending=False)
print(by_item)
daily = df.groupby("date").agg(orders=("order_id", "count"),
revenue=("revenue", "sum"))
print(daily)
print(df["category"].value_counts())Cold Brew leads with 660, then Espresso 600 and Cappuccino 540. The daily table has 2 orders each day, with revenue 420, 960, 600 and 480. value_counts() counts rows per category: coffee 6, bakery 1, tea 1.
pandas cheat sheet
| I want to... | pandas code |
|---|---|
| See the first rows | df.head() |
| Column types and missing counts | df.info() |
| Number summary | df.describe() |
| Keep rows by condition | df[df["col"] > 5] |
| New column | df["new"] = df["a"] * df["b"] |
| Join tables | a.merge(b, on="key", how="left") |
| Total per group | df.groupby("g")["v"].sum() |
| Count per value | df["col"].value_counts() |
| Save to CSV | df.to_csv("out.csv", index=False) |
When you do not know a pandas function, run help(df.groupby) or put your cursor in the brackets and press Shift+Tab in Colab or Jupyter to see the docs.
I have a pandas DataFrame called [df] with these columns: [list columns and their meaning]. Example rows:
[paste 3 rows]
Write pandas code to: [describe the task in plain English].
Requirements: use only pandas and NumPy, explain each line in a comment, and show what the result should look like for my example rows.What to expect: Run the code on your data and check one or two numbers by hand before trusting the whole result.
How to work through this lesson
- 1Open a Colab or Kaggle notebook and paste the NumPy cell. Run it and check the printed values match the comments.
- 2Paste the CSV cell, run it, and confirm
info()shows 8 rows and 1 missing value each inqtyandcity. - 3Run the fill cell, then the filter cell. Change a number in a condition and predict the result before you run it.
- 4Paste the menu and merge cell, then compare your table with the expected output.
- 5Run the groupby cell and check your totals against the numbers described below it.
Common mistakes
Check yourself
- Chained filters without parentheses around each condition.
- Using
=where you meant==in a filter. - Forgetting that most pandas methods return a new DataFrame; write
df = df.dropna()or the change is lost. - Writing a Python loop over rows when a column operation or
groupbydoes it in one line. - Merging on a key with different spellings (
Espressovsespresso), which silently drops matches.
Try it: Answer three questions from the data
Using the df you built: (1) what is the total revenue for the whole period, (2) which category earns the most revenue, (3) on which date was revenue highest?
Try it: Extend it
Add a column weekday using df["date"].dt.day_name(), then find the average revenue per weekday. Save the final table with to_csv.
Key takeaways
- NumPy does maths on whole arrays at once; pandas builds on it for tables.
- Start every dataset with
head(),info(),describe()andisna().sum(). - Filter with boolean conditions in brackets, combining with
&and|and parentheses. mergejoins tables;groupbysplits, applies and combines.- Handle missing values on purpose and write down what you chose.
Check yourself
Finishing the quiz marks the lesson as done.
1. Which line correctly keeps espresso orders with quantity greater than 1?
2. What does df.groupby("category")["revenue"].sum() return?
3. After df.dropna() on its own line, your DataFrame still has missing values. Why?
4. Why use how="left" when merging orders with the menu?
5. Why is prices * 2 on a NumPy array better than a loop?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.