Python · CSV · Descriptive statistics — 2026
Product Catalog Data Analysis
Pricing and availability profile of a 462-record product catalog, processed in pure Python.
Objective
How is list price distributed across categories, and how much of the catalog is actually purchasable right now?
A product export was parsed with Python's csv module and reduced with loops, dictionaries, and conditional logic — no dataframe library. Prices were cleaned and cast to numeric, records with missing or malformed prices were quarantined, and category-level averages were computed from the cleaned set.
- Records processed
- 462
- Available products
- 426
- Highest avg price
- $100.28
- Categories
- 6
Rows with a usable numeric price
92.2% of the catalog
Actuators category
After normalizing label casing
Analysis
Method and query logic
Step 1
Parse defensively
csv.DictReader reads the export; each price is stripped of currency symbols and thousands separators before a float cast inside a try/except.
Step 2
Quarantine bad rows
Rows that fail the cast are collected into a rejects list rather than silently dropped, so the excluded count is reportable.
Step 3
Aggregate with dictionaries
A category-keyed dictionary accumulates a running sum and count, giving averages in a single pass over the file.
Step 4
Profile availability
Availability flags are normalized to lowercase and counted, then expressed as a share of the cleaned record set.
import csv
from collections import defaultdict
totals = defaultdict(lambda: {"sum": 0.0, "count": 0})
rejects = []
available = 0
with open("products.csv", newline="", encoding="utf-8") as fh:
for row in csv.DictReader(fh):
raw = (row.get("price") or "").replace("$", "").replace(",", "").strip()
try:
price = float(raw)
except ValueError:
rejects.append(row.get("product_id"))
continue
category = (row.get("category") or "uncategorized").strip().title()
totals[category]["sum"] += price
totals[category]["count"] += 1
if (row.get("availability") or "").strip().lower() == "available":
available += 1
averages = {
cat: round(v["sum"] / v["count"], 2)
for cat, v in totals.items()
if v["count"]
}
for cat, avg in sorted(averages.items(), key=lambda kv: -kv[1]):
print(f"{cat:<14} {avg:>8.2f} (n={totals[cat]['count']})")
print(f"available: {available} rejected rows: {len(rejects)}")Results
Key findings
Average list price by category
Actuators lead on average list price at $100.28; the spread across categories is roughly 2.4x from top to bottom.
Price distribution
Counts by price band. The catalog is front-loaded under $75, so a flat average understates how many low-ticket items carry the volume.
- 462 records carried a usable numeric price after cleaning; malformed rows were logged rather than dropped silently.
- 426 products (92.2%) are flagged available, so catalog breadth and purchasable breadth are close but not identical.
- Actuators show the highest average list price at approximately $100.28.
- Just over half the catalog sits between $25 and $75, which pulls the overall mean below the perceived positioning of the top categories.
Business impact
Recommendation
Category averages alone would rank Actuators as the premium line, but the band distribution shows the revenue mix is likely driven by the $25-$75 middle.
The 7.8% unavailable share is small enough to ignore in a headline average and large enough to matter in a stock-out report — the right treatment depends on the question being asked.
Limitations
- — List price is not transaction price; no discount or margin data was available.
- — Category labels were normalized by casing only; near-duplicate labels would need a fuzzy match to fully consolidate.