Skip to main content
← All case studies

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

Rows with a usable numeric price

Available products
426

92.2% of the catalog

Highest avg price
$100.28

Actuators category

Categories
6

After normalizing label casing

Analysis

Method and query logic

  1. 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.

  2. 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.

  3. Step 3

    Aggregate with dictionaries

    A category-keyed dictionary accumulates a running sum and count, giving averages in a single pass over the file.

  4. Step 4

    Profile availability

    Availability flags are normalized to lowercase and counted, then expressed as a share of the cleaned record set.

Single-pass category aggregation with reject handlingpython
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.