Pandas interview questions with code you can run
In this article (4 sections)
Related programme: the Data Science course (6 months; ₹1,30,000 (inclusive of GST) · EMI from ₹5,000/month).
Pandas interview questions check whether you can reshape and summarise a table correctly and efficiently: grouping without losing rows you meant to keep, merging without multiplying rows, handling missing values on purpose, and avoiding slow row-by-row loops. The twelve questions below come with code tested on pandas 3.0 (it also runs on 2.x) and synthetic data, so you can test every answer and then vary it.
Grouping: questions 1 to 4
import pandas as pd
orders = pd.DataFrame({
"order_id": [1, 2, 3, 4, 5, 6, 7],
"city": ["Noida", "Noida", "Delhi", "Delhi", "Gurugram", None, "Noida"],
"amount": [1200, 800, 450, 2200, 990, 300, 1500],
"status": ["paid", "refunded", "paid", "paid", "paid", "paid", "paid"],
})
# Q1: total, mean and count of paid orders per city, with clear column names
paid = orders[orders["status"] == "paid"]
q1 = paid.groupby("city").agg(total=("amount", "sum"),
avg=("amount", "mean"),
n=("order_id", "count"))
print(q1)
# Q2: groupby drops missing keys by default; keep them explicitly
print(orders.groupby("city", dropna=False)["amount"].sum())
# Q3: each paid order's share of its city's paid total (same number of rows)
paid = paid.assign(city_share=paid["amount"] / paid.groupby("city")["amount"].transform("sum"))
print(paid[["order_id", "city", "amount", "city_share"]].round(3))
# Q4: largest order per city, keeping the whole row
print(orders.loc[orders.groupby("city")["amount"].idxmax()])Q1. Summarise paid orders by city with named outputs. Named aggregation, agg(total=("amount", "sum")), gives readable columns and avoids a MultiIndex. Checking: that you filter status before grouping — the refunded order should not count.
Q2. Order 6 has no city. Where did it go? groupby drops NaN keys by default. dropna=False keeps them as their own group. In a real report, missing city is a data-quality line that should be visible, not quietly excluded.
Q3. Share of city total on each row. transform("sum") broadcasts the group sum back to each row. The weak answer merges an aggregated table back in; it works but is longer and risks a bad join.
Q4. The largest order per city, whole row. idxmax returns the index label of the maximum per group, and .loc fetches those rows. An alternative is sort_values("amount").groupby("city").tail(1). Note that the missing-city row is not included, which the interviewer may ask about.
Joining and reshaping: questions 5 to 8
import pandas as pd
leads = pd.DataFrame({"phone": ["981", "982", "983"], "source": ["web", "ads", "web"]})
payments = pd.DataFrame({"phone": ["982", "982", "984"], "paid": [5000, 5000, 7000]})
# Q5: merge that reports where each row came from
m = leads.merge(payments, on="phone", how="outer", indicator=True)
print(m)
print(m["_merge"].value_counts())
# Q6: protect against an unexpected many-to-many
try:
leads.merge(payments, on="phone", validate="one_to_one")
except pd.errors.MergeError as e:
print("MergeError:", e)
# Q7: wide monthly targets to long format and back
wide = pd.DataFrame({"rep": ["Asha", "Ravi"], "Apr": [10, 12], "May": [11, 9]})
long = wide.melt(id_vars="rep", var_name="month", value_name="target")
print(long)
print(long.pivot_table(index="rep", columns="month", values="target", aggfunc="sum"))
# Q8: value_counts as proportions, including missing values
s = pd.Series(["web", "ads", "web", None, "web"])
print(s.value_counts(normalize=True, dropna=False).round(2))Q5. Which leads paid, which payments have no lead? indicator=True adds _merge with left_only, right_only and both. This is the reconciliation answer interviewers want: one merge, and a count of each category.
Q6. How do you make a merge fail loudly if keys are duplicated? validate="one_to_one" (or "one_to_many", "many_to_one") raises a MergeError when the key relationship is not what you assumed. Here phone 982 appears twice in payments — perhaps two instalments — so the check fires. Checking: defensive habits. Say what you would do next: decide whether two rows are two instalments or a duplicate, then aggregate payments by phone before merging.
Q7. Wide to long and back. melt for wide to long; pivot_table for long to wide with an aggregation, which handles duplicate index/column pairs where pivot would raise an error.
Q8. Share of each lead source, including unknown. value_counts(normalize=True, dropna=False). Without dropna=False, the missing source disappears and the other shares are inflated.
Dates, performance and correctness: questions 9 to 12
import pandas as pd
ts = pd.DataFrame({
"created_utc": pd.to_datetime(["2026-08-31 20:10", "2026-09-01 04:00",
"2026-09-02 12:00", "2026-09-09 09:30"], utc=True),
"amount": [1500, 2200, 650, 990],
})
# Q9: convert to IST, then weekly totals with weeks starting Monday
ts["created_ist"] = ts["created_utc"].dt.tz_convert("Asia/Kolkata")
weekly = ts.set_index("created_ist").resample("W-SUN")["amount"].sum()
print(weekly)
# Q10: vectorised condition instead of apply
ts["band"] = pd.cut(ts["amount"], bins=[0, 1000, 2000, float("inf")],
labels=["low", "mid", "high"], right=False)
print(ts[["amount", "band"]])
# Q11: avoid chained assignment; use .loc on the original frame
ts.loc[ts["amount"] < 1000, "flag"] = "review"
print(ts[["amount", "flag"]])Q9. Weekly totals in IST. Convert time zones before resampling. The 20:10 UTC order on 31 August is 01:40 IST on 1 September; here both dates fall in the same Monday-to-Sunday week, but an order at 20:10 UTC on a Sunday would move into the next week, and every daily figure shifts. resample("W-SUN") labels each week by its Sunday end date, so the week is Monday to Sunday.
Q10. Replace an `apply` with a lambda. pd.cut or numpy.select operate on the whole column at once and are far faster than a Python function called per row. right=False makes each bin include its left edge, so exactly ₹1,000 is "mid". State your boundary rule; interviewers probe it.
Q11. Why does `df[df.amount < 1000]["flag"] = "review"` not work? It is chained indexing: the first bracket may return a copy, and the assignment goes to that copy. Use a single .loc[row_mask, column]. Under copy-on-write, which is the default from pandas 3.0, chained assignment never modifies the original at all.
Q12. `loc` versus `iloc`. loc selects by label and includes the end of a slice; iloc selects by integer position and excludes it, like Python slicing. After filtering, index labels are no longer 0 to n−1, so df.loc[0] may fail where df.iloc[0] works. Call reset_index(drop=True) if positional labels matter.
Practising beyond the answers
For each block, break something on purpose: add a duplicate lead to question 5, remove utc=True in question 9, set right=True in question 10. Predict the output first. That is the conversation a good pandas interview becomes.
For problems framed as coding puzzles rather than table work, continue with the Python coding round for data roles. Pandas is used daily across the Data Science programme, from cleaning to feature pipelines.
Frequently asked questions
What is the difference between transform and agg in pandas groupby?
agg returns one row per group, such as total sales per city. transform returns a result with the same index as the original rows, so each row gets its group's value. Use transform when you want to compare every row with its group, such as each order's share of its city's total.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Science programme — 6 months. From data foundations to machine learning, deep learning and deployment.
Explore Data Science