Python for Data Analysis — Putting It Together¶
You now have Python fundamentals: variables, functions, lists, dicts, and file I/O. This file shows how these combine into real analytical work — before Pandas, and as a foundation for understanding what Pandas does under the hood.
Learning Objectives¶
- Write a complete data analysis pipeline in pure Python
- Calculate descriptive statistics from scratch
- Implement grouping, filtering, and aggregation without libraries
- Understand when to graduate from pure Python to Pandas
- Use Python's
statisticsandcollectionsstandard library modules
The Standard Library for Analytics¶
Python ships with useful modules — no pip install required.
import statistics # mean, median, stdev, variance
import collections # Counter, defaultdict
import itertools # chain, groupby, combinations
import datetime # date arithmetic
import math # floor, ceil, sqrt, log
statistics module¶
import statistics
revenues = [42000, 38500, 51200, 49800, 55100, 48250, 61000, 44500]
print(statistics.mean(revenues)) # 48793.75
print(statistics.median(revenues)) # 49025.0
print(statistics.stdev(revenues)) # 7076.68
print(statistics.variance(revenues)) # 50080000.21
print(statistics.mode([1, 2, 2, 3, 3, 3])) # 3
collections.Counter — frequency counting¶
from collections import Counter
statuses = ["completed", "pending", "completed", "cancelled",
"completed", "pending", "completed", "refunded"]
counts = Counter(statuses)
print(counts)
# Counter({'completed': 4, 'pending': 2, 'cancelled': 1, 'refunded': 1})
print(counts.most_common(3))
# [('completed', 4), ('pending', 2), ('cancelled', 1)]
# Frequency as percentage
total = sum(counts.values())
for status, count in counts.most_common():
print(f"{status}: {count} ({count/total:.1%})")
collections.defaultdict — group-by pattern¶
from collections import defaultdict
orders = [
{"customer": "C001", "total": 298.00, "status": "completed"},
{"customer": "C001", "total": 35.99, "status": "completed"},
{"customer": "C002", "total": 149.97, "status": "pending"},
{"customer": "C003", "total": 389.00, "status": "completed"},
{"customer": "C002", "total": 89.50, "status": "completed"},
]
# Group by customer
by_customer = defaultdict(list)
for order in orders:
by_customer[order["customer"]].append(order)
# Summarise each customer
for customer, customer_orders in sorted(by_customer.items()):
completed = [o for o in customer_orders if o["status"] == "completed"]
total_spend = sum(o["total"] for o in completed)
print(f"{customer}: {len(completed)} completed orders, £{total_spend:,.2f} spent")
Output:
C001: 2 completed orders, £333.99 spent
C002: 1 completed orders, £89.50 spent
C003: 1 completed orders, £389.00 spent
Building an Analytics Pipeline¶
The dataset¶
import csv
from pathlib import Path
from collections import defaultdict
import statistics
# Sample dataset (normally loaded from CSV)
orders = [
{"order_id": "O001", "customer_id": "C001", "product_id": "P001",
"month": "2024-01", "quantity": 2, "unit_price": 149.00, "status": "completed"},
{"order_id": "O002", "customer_id": "C003", "product_id": "P004",
"month": "2024-01", "quantity": 1, "unit_price": 389.00, "status": "completed"},
{"order_id": "O003", "customer_id": "C002", "product_id": "P003",
"month": "2024-01", "quantity": 3, "unit_price": 49.99, "status": "pending"},
{"order_id": "O004", "customer_id": "C001", "product_id": "P002",
"month": "2024-02", "quantity": 1, "unit_price": 35.99, "status": "completed"},
{"order_id": "O005", "customer_id": "C004", "product_id": "P001",
"month": "2024-02", "quantity": 1, "unit_price": 149.00, "status": "cancelled"},
{"order_id": "O006", "customer_id": "C003", "product_id": "P003",
"month": "2024-02", "quantity": 5, "unit_price": 49.99, "status": "completed"},
]
# Add computed total
for o in orders:
o["total"] = o["quantity"] * o["unit_price"]
Step 1 — Filter¶
completed = [o for o in orders if o["status"] == "completed"]
print(f"Completed orders: {len(completed)} of {len(orders)}")
Step 2 — Aggregate¶
totals = [o["total"] for o in completed]
print(f"Total revenue: £{sum(totals):,.2f}")
print(f"Average order: £{statistics.mean(totals):,.2f}")
print(f"Median order: £{statistics.median(totals):,.2f}")
print(f"Largest order: £{max(totals):,.2f}")
print(f"Smallest order: £{min(totals):,.2f}")
Step 3 — Group and compare¶
# Revenue by month
by_month = defaultdict(float)
for o in completed:
by_month[o["month"]] += o["total"]
print("\nMonthly Revenue:")
for month in sorted(by_month):
print(f" {month}: £{by_month[month]:,.2f}")
# Revenue by product
by_product = defaultdict(lambda: {"orders": 0, "units": 0, "revenue": 0.0})
for o in completed:
pid = o["product_id"]
by_product[pid]["orders"] += 1
by_product[pid]["units"] += o["quantity"]
by_product[pid]["revenue"] += o["total"]
print("\nProduct Performance:")
for pid, data in sorted(by_product.items(), key=lambda x: -x[1]["revenue"]):
print(f" {pid}: {data['orders']} orders, {data['units']} units, £{data['revenue']:,.2f}")
Step 4 — Export results¶
import csv
summary_rows = [
{"product_id": pid, **data}
for pid, data in sorted(by_product.items(), key=lambda x: -x[1]["revenue"])
]
with open("product_summary.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["product_id", "orders", "units", "revenue"])
writer.writeheader()
writer.writerows(summary_rows)
Descriptive Statistics from Scratch¶
Understanding how these work helps you know when Pandas gives you the wrong answer.
def describe(data, label=""):
"""Compute descriptive statistics for a list of numbers."""
n = len(data)
if n == 0:
return
sorted_data = sorted(data)
mean = sum(data) / n
# Median
mid = n // 2
if n % 2 == 0:
median = (sorted_data[mid - 1] + sorted_data[mid]) / 2
else:
median = sorted_data[mid]
# Standard deviation (population)
variance = sum((x - mean) ** 2 for x in data) / n
std = variance ** 0.5
# Percentiles
def percentile(sorted_list, p):
idx = (len(sorted_list) - 1) * p / 100
lower = int(idx)
frac = idx - lower
if lower + 1 < len(sorted_list):
return sorted_list[lower] + frac * (sorted_list[lower + 1] - sorted_list[lower])
return sorted_list[lower]
if label:
print(f"\n{label}")
print("-" * 30)
print(f"Count: {n}")
print(f"Mean: {mean:,.2f}")
print(f"Median: {median:,.2f}")
print(f"Std: {std:,.2f}")
print(f"Min: {sorted_data[0]:,.2f}")
print(f"25%: {percentile(sorted_data, 25):,.2f}")
print(f"75%: {percentile(sorted_data, 75):,.2f}")
print(f"Max: {sorted_data[-1]:,.2f}")
describe([o["total"] for o in completed], "Order Totals")
When to Graduate from Pure Python to Pandas¶
| Task | Pure Python | Pandas |
|---|---|---|
| Load a small CSV (< 10K rows) | Fine | Overkill |
| Load a large CSV (> 100K rows) | Slow | Use it |
| Filter rows | List comprehension | df[df["col"] > x] |
| Group and aggregate | defaultdict |
df.groupby() |
| Merge two datasets | Manual loop | pd.merge() |
| Reshape data (pivot) | Very tedious | df.pivot_table() |
| Time series analysis | Difficult | Built-in |
| Plot a chart | Possible but verbose | df.plot() |
Rule of thumb: if you have tabular data with more than a few thousand rows or more than 5-6 columns, switch to Pandas. The syntax overhead is worth it.
Practice Exercises¶
Warm-up
1. Using the orders list above, calculate the mean, median, and std of total for completed orders. Use the statistics module.
2. Use Counter to count how many orders fall into each status.
3. Use defaultdict to group orders by product_id and print the total revenue per product.
Main
4. Write a function monthly_summary(orders) that returns a dict of {month: {"revenue": ..., "orders": ..., "avg_order": ...}} for completed orders.
5. Write a function top_n_customers(orders, n=5) that returns the top N customers by total spend on completed orders, as a list of dicts sorted by spend descending.
6. Add a percentile_rank field to each completed order: what percentage of completed orders have a smaller total than this one?
Stretch 7. Implement a simple two-pass algorithm that detects outlier orders: any order whose total is more than 2 standard deviations above the mean is flagged as an outlier. 8. Write a complete mini-pipeline: load orders from a CSV, filter to completed, group by customer, compute summary stats per customer, and export to a new CSV — all in pure Python with no Pandas.