pandas is Python's main library for working with tables of data. Its DataFrame is like a spreadsheet in code: labelled columns, labelled rows, and fast operations on whole columns. It reads CSV, Excel, JSON and SQL, and makes filtering, grouping and summarising a few lines of code:
import pandas as pd
sales = pd.read_csv("market_sales.csv")
print(sales.groupby("zone")["units"].sum())Install it with python -m pip install pandas. This tutorial covers the DataFrame model, selecting with loc and iloc, filtering rows, grouping and aggregating, and handling missing values. The examples analyse sales at a weekend market. pandas is built on NumPy, and this lesson uses pandas 3.0 behaviour, noting where older versions differ.
Table model
A DataFrame is a table of columns; each column is a Series, a one-dimensional labelled array of one type. Both have an index, the row labels:
import pandas as pd
stalls = pd.DataFrame(
{"stall": ["Herbs", "Bread", "Honey", "Cheese"],
"zone": ["east", "west", "east", "west"],
"sales": [120.0, None, 85.5, 210.0]},
index=[101, 102, 103, 104],
)
print(stalls.shape) # (4, 3)
print(stalls.dtypes) # stall and zone: str, sales: float64
stalls.info() # columns, non-null counts and typesStart every analysis by inspecting the data: head(), shape, dtypes, info() and describe() reveal wrong types, unexpected blanks and odd values before they distort results. In pandas 3.0 text columns get a dedicated str dtype; earlier versions show object.
Row labels aren't necessarily positions. Here the index is the stall number, so label 101 is the first row. When you read a CSV without specifying an index, pandas uses 0, 1, 2..., which makes labels and positions coincide, until you filter or sort and they no longer do.
Selection
There are three main ways to select data:
| Syntax | Selects by | Example |
|---|---|---|
df["col"] / df[["a", "b"]] | column name(s) | stalls["sales"] |
df.loc[rows, cols] | labels | stalls.loc[103, "stall"] → "Honey" |
df.iloc[rows, cols] | integer positions | stalls.iloc[0, 0] → "Herbs" |
Filtering uses a boolean Series as a mask:
big = stalls[stalls["sales"] > 100]
east_big = stalls.loc[(stalls["zone"] == "east") & (stalls["sales"] > 100), ["stall", "sales"]]Combine conditions with & and |, each in brackets, not with and/or. .isin(["east", "north"]) tests membership, and .query("sales > 100") is a readable alternative.
To change values, use one loc call: stalls.loc[stalls["zone"] == "east", "sales"] *= 1.1. Chained assignment such as stalls[mask]["sales"] = 0 never modifies the original in pandas 3.0, because of copy-on-write; it raises a warning instead.
Aggregation
groupby splits rows into groups by one or more columns, applies a calculation to each group, and combines the results:
by_zone = stalls.groupby("zone")["sales"].sum()
summary = stalls.groupby("zone")["sales"].agg(["sum", "mean", "count"]) sum mean count
zone
east 205.5 102.75 2
west 210.0 210.00 1Choose the aggregation that answers the business question. Total sales per zone uses sum; a typical stall's sales uses mean or median; how many stalls reported uses count, which excludes missing values, while size() counts every row. In the example, west's mean of 210 is based on one stall, because Bread's sales are missing, which is exactly the kind of detail a report should state.
Other summaries: value_counts() counts categories, pivot_table() builds cross-tabulations, and sort_values() ranks results.
Missing values
pandas represents missing data as NaN (or NA). Detect it before deciding what it means:
print(stalls["sales"].isna().sum()) # 1 missing value
print(stalls["sales"].mean()) # 138.5: missing values are skipped
print(stalls["sales"].fillna(0).mean()) # 103.875: treats missing as zeroThe two means differ because they encode different assumptions. A missing sales figure might mean the stall sold nothing, didn't trade that day, or simply didn't report. Only the first justifies filling with 0. Options include:
dropna()to exclude incomplete rows, when they genuinely don't belong in the analysis;fillna(value)when there's a defensible default;- keeping the gap and reporting how many values were missing.
Write the policy down next to the analysis. Aggregations such as sum and mean skip missing values by default, which is usually right, but means a total can silently cover fewer rows than you think. The data cleaning lesson goes further.
Independent practice and a worked check
Task: create a market report with typed columns, a row filter, grouped totals and a documented missing value policy.
import pandas as pd
df = pd.DataFrame({
"stall": ["Herbs", "Bread", "Honey", "Cheese", "Soap"],
"zone": ["east", "west", "east", "west", "east"],
"units": [12, 30, None, 8, 15],
})
print(df.dtypes["units"]) # float64: the missing value forces floats
policy = "Rows with missing units are excluded and counted, not treated as zero."
known = df.dropna(subset=["units"])
busy = known[known["units"] >= 10]
print(busy.groupby("zone")["units"].sum().to_dict()) # {'east': 27.0, 'west': 30.0}
print(f"Excluded: {len(df) - len(known)} row(s). {policy}")For the worked check, the selector that uses integer positions is iloc; loc uses labels.
Reference and next practice
The pandas documentation's 10 minutes to pandas and indexing guide are good next steps. Continue with the Matplotlib tutorial and data cleaning. Practise with the Python exercises and Python quiz.
Pandas Tutorial Summary
- Table model: A DataFrame organizes labeled columns and rows while a Series represents one labeled sequence.
- Selection: loc selects by labels and iloc selects by integer positions.
- Aggregation: groupby partitions rows into groups before aggregation.
- Missing values: Missing data needs a deliberate interpretation before dropping or filling it.
Pandas Tutorial Examples
Each example shows the code, the output it produces and what each part does.
Pandas Tutorial in a small program
import pandas as pd
sales = pd.DataFrame({"zone": ["east", "west", "east"], "units": [6, 9, 4]})
print(sales.groupby("zone")["units"].sum().to_dict()){'east': 10, 'west': 9}Line-by-line explanation
- Line 2A DataFrame from a dictionary of columns.
- Line 3Group by zone, select the
unitscolumn, sum each group and convert to a plain dict.
A market report from CSV text
import io
import pandas as pd
csv_text = """stall,zone,units,price
Herbs,east,12,2.50
Bread,west,30,3.20
Honey,east,,6.00
Cheese,west,8,7.50
Soap,east,15,4.00
"""
sales = pd.read_csv(io.StringIO(csv_text))
print(sales.shape, sales["units"].isna().sum())
sales["revenue"] = sales["units"] * sales["price"]
report = (sales.dropna(subset=["units"])
.groupby("zone")
.agg(stalls=("stall", "count"), revenue=("revenue", "sum"))
.sort_values("revenue", ascending=False))
print(report)
print("Top stall:", sales.loc[sales["revenue"].idxmax(), "stall"])
print("Positions 0-1:", sales.iloc[0:2]["stall"].tolist())(5, 4) 1
stalls revenue
zone
west 2 156.0
east 2 90.0
Top stall: Bread
Positions 0-1: ['Herbs', 'Bread']Line-by-line explanation
- Line 11
read_csvaccepts a file path or any file-like object; the empty cell becomesNaN. - Line 14Column arithmetic is vectorized; Honey's revenue is
NaNbecause its units are missing. - Lines 15 to 18Rows without units are excluded explicitly, then named aggregations build the report.
- Line 20
idxmax()returns the index label of the largest revenue, whichlocthen uses.
Common Pandas Tutorial Mistakes
Mixing up loc and iloc after filtering
east = stalls[stalls["zone"] == "east"] # index labels 101 and 103
print(east.loc[0]) # KeyError: there is no label 0print(east.iloc[0]) # first row by position
print(east.loc[101]) # row with label 101Filling missing sales with zero by default
stalls["sales"] = stalls["sales"].fillna(0) # unreported becomes "sold nothing"missing = stalls["sales"].isna().sum()
average = stalls["sales"].mean() # skips missing values
print(f"Average of reporting stalls: {average:.2f} ({missing} not reported)")Pandas Tutorial in Real Projects
pandas in data work
Analysts use pandas to load exports from spreadsheets, databases and APIs, clean them, join tables (merge), compute summaries and feed charts or machine-learning models. Reports that once took a morning in a spreadsheet become a script that runs in seconds and gives the same answer every time.
Jupyter notebooks are the usual environment for exploration, with the finished analysis moved into scripts or pipelines. For very large data, libraries with a similar API, such as Polars, DuckDB and Dask, handle more rows or run faster, and the concepts from this lesson transfer directly.
Pandas Tutorial FAQ
What is the difference between loc and iloc in pandas?
loc selects by labels, such as index values and column names. iloc selects by integer positions, starting at 0. After filtering or sorting, labels and positions often differ, so pick the one that matches your intent.
What is the difference between a DataFrame and a Series?
A Series is one labelled column of values. A DataFrame is a table of several Series sharing the same row index. Selecting one column, df["sales"], returns a Series.
How do I read a CSV file with pandas?
df = pd.read_csv("file.csv"). Useful options include sep=";" for other delimiters, parse_dates=["date"] for date columns, dtype={"code": str} to keep leading zeros, and encoding="utf-8-sig" for Excel exports.
Why does my integer column become float in pandas?
NumPy integer columns can't hold NaN, so a column with missing values is stored as float64. Use the nullable integer type, df["units"].astype("Int64"), if you need integers with missing values.
What is SettingWithCopyWarning or ChainedAssignmentError?
They warn that you assigned to a temporary copy, as in df[df.x > 1]["y"] = 0, which doesn't change df. Use a single loc call instead: df.loc[df.x > 1, "y"] = 0. pandas 3.0 uses copy-on-write, so chained assignment never updates the original.
Pandas Tutorial Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Which selector uses integer positions?
Which review concern is valid for pandas tutorial?
Pandas Tutorial Quiz
2 questions to check what you learned.
Which selector uses integer positions?
- loc
- iloc
- Python ignores the distinction
Which statement correctly describes aggregation?
- groupby partitions rows into groups before aggregation.
- Only the example output matters, not the rule
- Every operation has the same behavior