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.
Content checked 2026.09.20Hand-checked example
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.
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.
Measure
Source total
Orders
7
Units
18
Amount
430
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.
month
orders
units
amount
2026-01
2
5
100
2026-02
3
7
175
2026-03
2
6
155
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.
06Reconcile the result and recognize common errors
Count the source rows: 7.
Add monthly orders: 2 + 3 + 2 = 7.
Add monthly units: 5 + 7 + 6 = 18.
Add monthly amount: 100 + 175 + 155 = 430.
Confirm that the output contains exactly 2026-01, 2026-02, and 2026-03.
Run the script again without changing OUTPUT_DIR. It should stop with FileExistsError instead of replacing the existing result.
Symptom
What to check
ModuleNotFoundError for pandas
Install pandas into the same Python environment with python -m pip install pandas.
Missing required columns
Check the CSV header before changing the expected schema.
Date parsing error
Confirm that every order_date uses YYYY-MM-DD.
Numeric conversion error
Inspect units and amount for text, currency symbols, or separators that require an explicit cleanup rule.
Duplicate order_id found
Determine whether the repeated ID is an accidental duplicate or represents a different data model.
FileExistsError
Review 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.
Merge worksheets that use the same columns into one Excel table while recording which sheet each row came from. A small synthetic workbook lets you verify the row order, counts, and totals by hand.
Merge several PDFs into one file, extract an inclusive page range into a second file, and verify the resulting page counts and order. A synthetic set of blank pages with deliberately different dimensions makes the result checkable by hand.
Convert several known date formats into ISO-style YYYY-MM-DD values while keeping the original CSV unchanged. Values that do not match an approved format or are not valid calendar dates are written to a separate review file.
Find rows with exact matches in all four columns and collect them in a new Excel file with their original row numbers. Review each duplicate group, including its first occurrence.