Excel and document tasks

Build a monthly summary with pandas groupby and check the totals

Summarize order-level CSV data by month with pandas, then reconcile the grouped counts, units, and amounts against the source. A small synthetic example makes every expected result easy to verify by hand.

Show contents

Who this is forThis guide is for people who need a repeatable monthly CSV summary and want explicit checks that aggregation did not lose records.

What you need
  • Python 3.12 and a terminal command that starts that version.
  • pandas installed with python -m pip install pandas.
  • A working folder where the script can create a new folder beneath outputs.
  • The source CSV must contain order_id, order_date, units, and amount columns.

01Define the monthly measures

This example produces one row per calendar month with three measures: order count, total units, and total amount. The synthetic source uses one row per order, so counting rows is equivalent to counting orders in this specific dataset.

The important step is reconciliation. After groupby, the monthly values are added again and compared with the source totals. A summary can look reasonable even when rows were accidentally filtered, duplicated, or dropped.

02Create the synthetic CSV

The following dataset is synthetic and was written specifically for this article. Save it as monthly_orders.csv. It contains seven orders across January, February, and March 2026.

csv
order_id,order_date,team,units,amount
O001,2026-01-05,North,2,40
O002,2026-01-20,South,3,60
O003,2026-02-02,North,1,25
O004,2026-02-18,South,4,100
O005,2026-02-28,North,2,50
O006,2026-03-03,South,5,125
O007,2026-03-15,North,1,30

The source totals are simple enough to check manually. There are 7 orders. Units total 18: 2 + 3 + 1 + 4 + 2 + 5 + 1. Amount totals 430: 40 + 60 + 25 + 100 + 50 + 125 + 30.

MeasureSource total
Orders7
Units18
Amount430

03Calculate the expected monthly rows by hand

January contains O001 and O002. February contains O003, O004, and O005. March contains O006 and O007. Adding each group gives the expected monthly table.

monthordersunitsamount
2026-0125100
2026-0237175
2026-0326155

The monthly totals reconcile with the source: 2 + 3 + 2 = 7 orders, 5 + 7 + 6 = 18 units, and 100 + 175 + 155 = 430 amount.

04Build and verify the summary with pandas

Save the following script as monthly_summary_pandas.py. It validates required columns, rejects missing or duplicate order IDs, parses dates with one explicit format, converts numeric columns, groups by month, reconciles the totals, and writes the result only after those checks pass.

python
from pathlib import Path
import pandas as pd

SOURCE = Path("monthly_orders.csv")
OUTPUT_DIR = Path("outputs") / "monthly_summary_result"
OUTPUT = OUTPUT_DIR / "monthly_summary.csv"
REQUIRED = ["order_id", "order_date", "units", "amount"]


def main() -> None:
    if not SOURCE.is_file():
        raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    data = pd.read_csv(SOURCE, dtype={"order_id": "string"})

    missing = [name for name in REQUIRED if name not in data.columns]
    if missing:
        raise ValueError(f"Missing required columns: {missing}")
    if data.empty:
        raise ValueError("Source CSV contains no data rows.")
    if data[REQUIRED].isna().any().any():
        raise ValueError("A required field contains a missing value.")
    if data["order_id"].str.strip().eq("").any():
        raise ValueError("order_id contains a blank value.")
    if data["order_id"].duplicated().any():
        raise ValueError("Duplicate order_id found.")

    data["order_date"] = pd.to_datetime(
        data["order_date"], format="%Y-%m-%d", errors="raise"
    )
    data["units"] = pd.to_numeric(data["units"], errors="raise")
    data["amount"] = pd.to_numeric(data["amount"], errors="raise")
    data["month"] = data["order_date"].dt.to_period("M").astype(str)

    summary = (
        data.groupby("month", as_index=False, sort=True)
        .agg(
            orders=("order_id", "count"),
            units=("units", "sum"),
            amount=("amount", "sum"),
        )
    )

    source_totals = (len(data), data["units"].sum(), data["amount"].sum())
    summary_totals = (
        summary["orders"].sum(),
        summary["units"].sum(),
        summary["amount"].sum(),
    )
    if summary_totals != source_totals:
        raise RuntimeError("Monthly totals do not reconcile with the source.")

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()
    summary.to_csv(OUTPUT, index=False)

    check = pd.read_csv(OUTPUT)
    if check.to_dict("records") != summary.to_dict("records"):
        raise RuntimeError("Saved CSV does not match the calculated summary.")

    print(f"Source rows: {len(data)}.")
    print(f"Months: {len(summary)}.")
    print(f"Verified totals: units={source_totals[1]}, amount={source_totals[2]}.")
    print(f"Output: {OUTPUT.as_posix()}")


if __name__ == "__main__":
    main()
text
python monthly_summary_pandas.py

05Compare the expected output

The output should contain three data rows sorted by month. The expected CSV contents are shown below.

csv
month,orders,units,amount
2026-01,2,5,100
2026-02,3,7,175
2026-03,2,6,155

The expected console output below was derived by hand from the synthetic data and code. It is not a captured execution log.

text
Source rows: 7.
Months: 3.
Verified totals: units=18, amount=430.
Output: outputs/monthly_summary_result/monthly_summary.csv

06Reconcile the result and recognize common errors

  1. Count the source rows: 7.
  2. Add monthly orders: 2 + 3 + 2 = 7.
  3. Add monthly units: 5 + 7 + 6 = 18.
  4. Add monthly amount: 100 + 175 + 155 = 430.
  5. Confirm that the output contains exactly 2026-01, 2026-02, and 2026-03.
  6. Run the script again without changing OUTPUT_DIR. It should stop with FileExistsError instead of replacing the existing result.
SymptomWhat to check
ModuleNotFoundError for pandasInstall pandas into the same Python environment with python -m pip install pandas.
Missing required columnsCheck the CSV header before changing the expected schema.
Date parsing errorConfirm that every order_date uses YYYY-MM-DD.
Numeric conversion errorInspect units and amount for text, currency symbols, or separators that require an explicit cleanup rule.
Duplicate order_id foundDetermine whether the repeated ID is an accidental duplicate or represents a different data model.
FileExistsErrorReview the previous result and use a fresh output folder rather than overwriting it.

Matching grand totals do not prove that every month is correct because two monthly errors can offset each other. For important reports, also inspect several known source rows and month-level subtotals.

07Understand the limits

The example assumes one row per order and whole-number amounts. If one order spans several line-item rows, use a rule such as order_id.nunique() instead of row count. Real monetary data may also need fixed-point integer units or Decimal-based processing instead of ordinary floating-point arithmetic.

Month is derived directly from a date with no time-zone handling. If real data contains timestamps from several zones, define the business time zone before grouping. Refunds, cancellations, filters, and missing records also need explicit business rules; arithmetic reconciliation alone cannot determine whether those rows belong in the report.

Execution and verification record

2026-09-20 · hand-checked example · target: Python 3.12 · pandas required · no execution

  • Manually counted 7 source records across January, February, and March 2026.
  • Manually calculated source totals of 18 units and 430 amount.
  • Manually calculated January as 2 orders, 5 units, 100 amount; February as 3 orders, 7 units, 175 amount; and March as 2 orders, 6 units, 155 amount.
  • Manually reconciled the monthly totals back to 7 orders, 18 units, and 430 amount.
  • Inspected the script for required-column checks, duplicate IDs, explicit date parsing, numeric conversion, groupby aggregation, reconciliation, saved-file comparison, and output collision protection.
  • Derived the expected CSV and console output by hand.
Verification limits
  • The code was not executed by the author of this response; pandas behavior and filesystem writes were not tested here.
  • Decimal currency, time zones, refunds, cancellations, line-item order data, and very large files were not tested.
  • The official documentation URLs were provided from known documentation locations but were not checked live.

Site-wide writing and verification principles

References

The explanations and examples were written for this site. See the official sources below for the related behavior and concepts.