Data Tools

Three pandas projects for beginners, with complete code and output

Build three pandas projects with complete Python code: clean orders, join product tables and calculate a daily trend. Check outputs and common data mistakes.

Start with pandas projects that answer a concrete question: which orders are valid, which records match a product catalogue, and how study time changes across a calendar. These three beginner projects teach cleaning, grouping, joining and rolling averages with complete code and outputs you can check.

You need basic Python: variables, lists, dictionaries and running a script. If those are new, begin with Python projects for beginners. Each dataset below is invented for teaching and included in the code, so you can work without downloading data or connecting an account.

Choose a pandas project

ProjectQuestion to answerpandas skillsExpected check
Clean an order reportWhat revenue can the valid records support?Read CSV, remove duplicates, convert values, group rowsValid rows sum to 48
Join a product catalogueWhich orders have a known product?Left join, match indicators, join validationOne unknown SKU; matched revenue 21
Track a daily trendWhat happens when a date is missing?Dates, calendar reindexing, rolling meansTwo complete three-day averages

In this article:

Set up pandas

Create a project folder and a virtual environment using python -m venv .venv. Activate it with source .venv/bin/activate on macOS/Linux, or .venv\Scripts\Activate.ps1 in Windows PowerShell. Then install pandas and run a saved example:

python -m pip install pandas
python clean_orders.py

You can also paste an example into a notebook cell. The code and outputs below were checked with Python 3.12.3 and pandas 3.0.6. For a first introduction to labelled tables, use Series and DataFrames.

Project 1: clean an order report

The task: calculate revenue by category while keeping an audit of duplicate and invalid records. Each order in this invented dataset is supposed to occupy one row; amounts are in arbitrary currency units.

One order is repeated, one price is blank and another price contains text. Save this as clean_orders.py:

from io import StringIO
import pandas as pd

csv_text = """order_id,category,units,unit_price
1,books,2,12
2,stationery,3,4
3,books,1,12
4,stationery,2,
2,stationery,3,4
5,books,1,oops
"""
raw = pd.read_csv(StringIO(csv_text))
orders = raw.drop_duplicates(subset="order_id").copy()
for column in ["units", "unit_price"]:
    orders[column] = pd.to_numeric(orders[column], errors="coerce")

valid = orders["units"].gt(0) & orders["unit_price"].ge(0)
rejected = orders.loc[~valid]
clean = orders.loc[valid].copy()
clean["revenue"] = clean["units"] * clean["unit_price"]
summary = clean.groupby("category", as_index=False)["revenue"].sum()

print(f"Duplicate rows removed: {len(raw) - len(orders)}")
print("Rejected order IDs:", rejected["order_id"].tolist())
for row in summary.itertuples(index=False):
    print(f"{row.category}: {row.revenue:.2f}")
print(f"Total revenue from valid rows: {clean['revenue'].sum():.2f}")
assert clean["revenue"].sum() == 48

Expected output:

Duplicate rows removed: 1
Rejected order IDs: [4, 5]
books: 36.00
stationery: 12.00
Total revenue from valid rows: 48.00

The duplicate order 2 is identical, so keeping one copy is reasonable for this example. In your own data, check whether repeated IDs represent separate line items or corrections before dropping them.

to_numeric(errors="coerce") turns unreadable values into missing values. The validity rule accepts positive units and non-negative prices. That rejects orders 4 and 5, which have no usable price. It does not invent replacement prices. The report's total of 48 applies only to the three valid rows, not to every original order.

Try it yourself: replace the blank price on order 4 with 4. Stationery revenue should become 20 and valid revenue should become 56. Then add a negative quantity and confirm it is rejected. Update the check for the changed input and record why a rejected row was excluded.

The cleaning data and groupby and aggregation lessons explain these operations. Keep the public pandas cheat sheet beside you while changing the code.

Project 2: join orders to products

The task: attach a product name and price to each order without creating extra order rows. A SKU is a product identifier, such as N1 for a notebook.

Several orders can share a SKU, but each SKU should appear only once in this product catalogue. Save this as join_products.py:

import pandas as pd

orders = pd.DataFrame({
    "order_id": [1, 2, 3, 4],
    "sku": ["N1", "N1", "P1", "X9"],
    "quantity": [2, 1, 3, 1],
})
products = pd.DataFrame({
    "sku": ["N1", "P1"],
    "product": ["Notebook", "Pen"],
    "unit_price": [5, 2],
})

joined = orders.merge(
    products, on="sku", how="left", validate="many_to_one", indicator=True
)
unknown = joined.loc[joined["_merge"].eq("left_only")]
matched = joined.loc[joined["_merge"].eq("both")].copy()
matched["revenue"] = matched["quantity"] * matched["unit_price"]

print(f"Orders before join: {len(orders)}")
print(f"Rows after join: {len(joined)}")
print("Unknown SKUs:", unknown["sku"].tolist())
print(f"Revenue from matched orders: {matched['revenue'].sum():.2f}")
assert len(joined) == len(orders)
assert matched["revenue"].sum() == 21

Expected output:

Orders before join: 4
Rows after join: 4
Unknown SKUs: ['X9']
Revenue from matched orders: 21.00

A left join keeps every order, including the one whose SKU has no catalogue match. The indicator column makes that missing match visible. An inner join would remove that order from the result, so you could miss the fact that the catalogue is incomplete.

validate="many_to_one" tells pandas that many order rows may match one product row. If the product table unexpectedly contains two rows for the same SKU, the merge raises an error instead of multiplying matching order rows. The total of 21 covers the matched orders only; X9's price is still unknown.

Try it yourself: add X9 to the catalogue with a unit price of 7. There should still be four joined rows, no unknown SKUs and revenue of 28. Then deliberately duplicate N1 in the catalogue and observe the validation error before deciding how to repair the source data.

Work through merging, joining and reshaping to practise choosing a join and checking its effect on row counts.

Project 3: calculate a daily moving average

The task: report a three-day moving average of study minutes, while distinguishing a recorded zero from an unrecorded day. A moving average summarises a window that advances through the data.

The example has no observation for October 3. October 5 has a genuine recorded value of zero. Save this as daily_trend.py:

import pandas as pd

sessions = pd.DataFrame({
    "date": ["2026-10-01", "2026-10-02", "2026-10-04",
             "2026-10-05", "2026-10-06", "2026-10-07"],
    "minutes": [20, 40, 60, 0, 40, 20],
})
sessions["date"] = pd.to_datetime(sessions["date"])
calendar = pd.date_range("2026-10-01", "2026-10-07", freq="D")
daily = sessions.set_index("date").sort_index().reindex(calendar)
daily["three_day_mean"] = daily["minutes"].rolling(3, min_periods=3).mean()

print(f"Days with no observation: {daily['minutes'].isna().sum()}")
for date, average in daily["three_day_mean"].dropna().items():
    print(f"{date:%Y-%m-%d}: {average:.2f} minutes")
assert daily["three_day_mean"].notna().sum() == 2

Expected output:

Days with no observation: 1
2026-10-06: 33.33 minutes
2026-10-07: 20.00 minutes

Reindexing creates one row for every date in the chosen calendar. The missing date gets a missing value, often displayed as NaN, which is a marker for an unavailable number. Because there is now one row per calendar day, a three-row window represents three consecutive days.

min_periods=3 requires three observed values before reporting a mean. That leaves the first complete window at October 6: (60 + 0 + 40) / 3 = 33.33. The next mean is (0 + 40 + 20) / 3 = 20. This example assumes at most one observation per date; aggregate multiple sessions per day before reindexing your own data.

Try it yourself: add an observed 30 minutes for October 3. The average ending October 3 should become 30, and five complete windows should be available. Separately, try filling the original gap with zero and explain why that changes the meaning of the data.

Continue with dates and time series in pandas. For reporting mistakes that can survive correct code, read misleading statistics examples.

Build your next version

Keep the first extension small enough to check:

  1. Replace the embedded data with a CSV you created or have permission to use.
  2. Write down the meaning of one row, each key and every missing value.
  3. Print the number of rows before and after cleaning or joining.
  4. Save a report that states which rows were excluded and why.
  5. Explain one result in plain language, including what the data cannot establish.

Once your report is reliable, add a chart through the visualisation module. If your next goal is modelling, read machine learning code in Python and keep preprocessing separate from test data.

Common questions

Which pandas project is best for a beginner?

Start with the order report. It is small enough to recalculate by hand and introduces the cleaning and aggregation steps you will reuse. Do the join project next, then the time-series example.

Can I build these projects without machine learning?

Yes. These are data-analysis projects. Their success criteria are correct records, trustworthy totals and a report someone can understand. You do not need to train a model to make them useful.

Where can I get pandas projects with source code?

Each project above includes its complete source code and data. Copy a block into the named Python file or a notebook cell. For a GitHub portfolio, publish your own extension with its data assumptions, setup instructions and checks; a link to copied code alone does not explain your work.

Should I learn NumPy before pandas?

You can start these pandas examples with basic Python. Studying arrays helps explain numerical operations and shapes as your work grows. Try NumPy projects for beginners alongside the Data Tools learning path.

Continue with the Data Tools course

Start the pandas module to learn selection, cleaning, grouping, joins and dates in sequence. Open lesson parts and key terms are free to read. A free account adds quizzes and saves your progress.

The opening modules' free sample lets you try the course's practice material. Compare the plans when you want more structured practice: Pro adds practice for every lesson, projects, certificates and weekly live sessions.

References

These examples use original teaching data. Refer to the official pandas documentation for missing data, grouping and aggregation, merge validation and rolling windows.

Related course

The four libraries every machine learning notebook uses: NumPy arrays and broadcasting, pandas tables, readable Matplotlib charts, and the scikit-learn API.

Open lessons' parts and key terms are free to read, with no account.

What you will be able to do

  • Reason about an array's shape and dtype before an operation runs.
  • Select, filter, group and join tables with pandas.
  • Make charts that answer one question clearly.
  • Use scikit-learn's fit, transform and predict with confidence.