Tutorial

Python Programs for Practice: 20 Real Analytics Scripts

Practice 20 real Python programs for data analytics. Master data cleaning, list comprehensions, dictionary aggregations, loops, and pandas DataFrames.

Anuj SainiSep 12, 202624 min read

Most programming tutorials teach syntax through toy puzzles: reversing palindromes, computing Fibonacci numbers, or printing asterisk Christmas trees. While these exercises introduce loops and conditionals, they fail to prepare you for actual analytical work.

In a business environment, data analysts do not spend their days printing stars. They sanitize malformed telephone records, extract deeply nested payloads from third-party payment APIs, identify revenue anomalies using statistical z-scores, and calculate customer acquisition cohort retention.

This guide provides 20 battle-tested Python programs for practice, spanning core data wrangling, algorithmic statistical modeling, tabular pandas workflows, and commercial analytics. Every program includes:

  1. The Business Objective & Sample Input Data
  2. Production-Ready Python 3 Code (commented and runnable)
  3. Line-by-Line Logic Breakdown
  4. Verified Terminal Output

The Python Analytics Execution Pipeline

Before diving into the code, it is essential to visualize how Python processes data from raw ingestion to commercial insights:

┌────────────────────────┐      ┌─────────────────────────┐      ┌─────────────────────────┐      ┌───────────────────────────┐
│ Raw Ingestion Layer    │ ───> │ In-Memory Wrangling     │ ───> │ Vectorized Calculations │ ───> │ Commercial Intelligence   │
│ REST APIs (JSON)       │      │ Pure Python Sets & Dicts│      │ pandas DataFrames       │      │ Cohort Retention Matrices │
│ PostgreSQL & SQLite    │      │ Regex String Sanitation │      │ GroupBy & Aggregations  │      │ Churn Risk & RFM Segments │
│ CSV / Excel Exports    │      │ Defensive Type Coercion │      │ Rolling Window Stats    │      │ Executive KPI Summaries   │
└────────────────────────┘      └─────────────────────────┘      └─────────────────────────┘      └───────────────────────────┘

When building analytical pipelines, remember two architectural rules:

  1. Use Pure Python for Ingestion & Unstructured Data: Dictionaries, list comprehensions, and sets excel at parsing nested JSON, streaming log files, and validating schema keys before building tabular memory structures.
  2. Use Vectorized Pandas for Tabular Math: Once data is structured into rows and columns, avoid Python for loops. Pandas and NumPy execute array operations at C-speed, running up to 300x faster than iterative loops.

If you are new to data analysis, explore Topfolio's Free Python Course for Data Analytics or review the complete Data Analyst Roadmap to see where Python connects with SQL and business intelligence.


Part 1: Core Data Wrangling & String Cleaning (Programs 1–5)

Raw data exported from production applications is notoriously messy. These first five programs demonstrate how to scrub, normalize, and validate unstructured inputs.

Program 1: Sanitizing Customer Phone Numbers to E.164 Format

Business Objective & Input Data

CRM systems frequently collect telephone numbers with inconsistent formatting, stray hyphens, spaces, and missing international country codes. This script parses raw phone inputs into standard E.164 format (+91XXXXXXXXXX for India or +1XXXXXXXXXX for North America).

python
import re
 
raw_phone_records = [
    {"user_id": 101, "phone": "+91 98765-43210"},
    {"user_id": 102, "phone": "098765 43210"},
    {"user_id": 103, "phone": "9876543210"},
    {"user_id": 104, "phone": "+1 (555) 234-5678"},
    {"user_id": 105, "phone": "invalid_number_123"},
]
 
def sanitize_phone_number(raw_phone: str, default_country_code: str = "91") -> str | None:
    """Strips formatting artifacts and enforces E.164 phone numbering standards."""
    # Strip all characters except digits and leading plus
    has_plus = raw_phone.strip().startswith("+")
    digits_only = re.sub(r"\D", "", raw_phone)
    
    # Strip leading trunk zeros (common in UK, India, Australia)
    if digits_only.startswith("0") and len(digits_only) == 11:
        digits_only = digits_only[1:]
 
    # Handle standard 10-digit mobile numbers without country codes
    if len(digits_only) == 10:
        return f"+{default_country_code}{digits_only}"
    
    # Handle numbers that already included country code (e.g. 12 digits for 91 + 10 digits)
    if len(digits_only) in (11, 12):
        return f"+{digits_only}"
        
    return None  # Unparseable record
 
cleaned_records = []
for record in raw_phone_records:
    normalized = sanitize_phone_number(record["phone"])
    cleaned_records.append({
        "user_id": record["user_id"],
        "original": record["phone"],
        "e164_phone": normalized,
        "is_valid": normalized is not None
    })
 
for rec in cleaned_records:
    print(f"User {rec['user_id']}: {rec['original']:<20} -> {rec['e164_phone']} (Valid: {rec['is_valid']})")

Logic Breakdown

  • re.sub(r"\D", "", raw_phone) matches every non-digit character (parentheses, spaces, dashes) and replaces it with an empty string.
  • The conditional digits_only.startswith("0") removes local trunk dialing prefixes before attaching the ISO country code.
  • If the normalized string contains exactly 10 digits, the default country code prefix is prepended.

Terminal Output

User 101: +91 98765-43210      -> +919876543210 (Valid: True)
User 102: 098765 43210         -> +919876543210 (Valid: True)
User 103: 9876543210           -> +919876543210 (Valid: True)
User 104: +1 (555) 234-5678    -> +15552345678 (Valid: True)
User 105: invalid_number_123   -> None (Valid: False)

Program 2: Flattening Nested JSON API Payloads

Business Objective & Input Data

Modern data pipelines ingest webhooks and REST API responses from Stripe, Shopify, or Razorpay. These payloads nest customer, billing, and line-item details in hierarchical dictionaries. This script flattens arbitrary nested JSON into tabular records.

python
from typing import Any
 
api_payload = {
    "order_id": "ORD-88219",
    "timestamp": "2026-09-12T14:32:00Z",
    "customer": {
        "id": "CUST-402",
        "profile": {
            "first_name": "Aarav",
            "last_name": "Patel",
            "email": "aarav.patel@enterprise.in"
        }
    },
    "payment": {
        "gateway": "Razorpay",
        "currency": "INR",
        "amount_paise": 249900,
        "status": "captured"
    }
}
 
def flatten_dictionary(nested_dict: dict[str, Any], parent_key: str = "", separator: str = "_") -> dict[str, Any]:
    """Recursively flattens nested dictionaries into a single-level key-value mapping."""
    flat_record = {}
    for key, value in nested_dict.items():
        new_key = f"{parent_key}{separator}{key}" if parent_key else key
        if isinstance(value, dict):
            flat_record.update(flatten_dictionary(value, parent_key=new_key, separator=separator))
        else:
            flat_record[new_key] = value
    return flat_record
 
flattened = flatten_dictionary(api_payload)
for flat_key, flat_val in flattened.items():
    print(f"{flat_key:<32}: {flat_val}")

Logic Breakdown

  • The function inspects each key-value pair. If isinstance(value, dict) evaluates to True, it recursively calls itself, appending the current key name with an underscore separator.
  • Base cases (strings, numbers, booleans) are inserted directly into the flat dictionary.

Terminal Output

order_id                        : ORD-88219
timestamp                       : 2026-09-12T14:32:00Z
customer_id                     : CUST-402
customer_profile_first_name     : Aarav
customer_profile_last_name      : Patel
customer_profile_email          : aarav.patel@enterprise.in
payment_gateway                 : Razorpay
payment_currency                : INR
payment_amount_paise            : 249900
payment_status                  : captured

Program 3: Deduplicating Event Streams with Composite Keys

Business Objective & Input Data

Webhooks often deliver duplicate messages when networks timeout and retry. This program processes a stream of transaction events, isolates composite business keys (account_id, invoice_id), and eliminates duplicate occurrences while maintaining original sequence order.

python
raw_events = [
    {"event_id": 1, "account_id": "ACC-01", "invoice_id": "INV-100", "amount": 4500},
    {"event_id": 2, "account_id": "ACC-02", "invoice_id": "INV-101", "amount": 9200},
    {"event_id": 3, "account_id": "ACC-01", "invoice_id": "INV-100", "amount": 4500},  # Duplicate
    {"event_id": 4, "account_id": "ACC-03", "invoice_id": "INV-102", "amount": 1250},
    {"event_id": 5, "account_id": "ACC-02", "invoice_id": "INV-101", "amount": 9200},  # Duplicate
]
 
def deduplicate_events(events: list[dict], key_fields: tuple[str, ...]) -> tuple[list[dict], int]:
    """Filters duplicate dictionary records in O(N) time using an in-memory hash set."""
    seen_keys = set()
    deduped = []
    duplicate_count = 0
    
    for event in events:
        composite_key = tuple(event.get(k) for k in key_fields)
        if composite_key in seen_keys:
            duplicate_count += 1
            continue
        seen_keys.add(composite_key)
        deduped.append(event)
        
    return deduped, duplicate_count
 
unique_records, duplicates_dropped = deduplicate_events(raw_events, ("account_id", "invoice_id"))
print(f"Total Processed: {len(raw_events)} | Duplicates Dropped: {duplicates_dropped}\n")
for record in unique_records:
    print(record)

Logic Breakdown

  • Python sets provide $O(1)$ average-time complexity lookups using hash tables.
  • By constructing a tuple of the composite keys (event['account_id'], event['invoice_id']), we create an immutable, hashable identifier.
  • Any event whose composite key already exists in seen_keys is incremented to duplicate_count and bypassed.

Terminal Output

Total Processed: 5 | Duplicates Dropped: 2

{'event_id': 1, 'account_id': 'ACC-01', 'invoice_id': 'INV-100', 'amount': 4500}
{'event_id': 2, 'account_id': 'ACC-02', 'invoice_id': 'INV-101', 'amount': 9200}
{'event_id': 4, 'account_id': 'ACC-03', 'invoice_id': 'INV-102', 'amount': 1250}

Program 4: Extracting & Validating Corporate Email Domains

Business Objective & Input Data

B2B software companies prioritize sales leads originating from corporate domains over generic personal inboxes (Gmail, Yahoo, Hotmail). This script extracts email domains, checks syntax validity, and flags whether the lead is enterprise or personal.

python
import re
 
leads = [
    {"lead_id": 1, "email": "priya.sharma@swiggy.in"},
    {"lead_id": 2, "email": "vikram99@gmail.com"},
    {"lead_id": 3, "email": "rohit@tcs.com"},
    {"lead_id": 4, "email": "bad_email_format@"},
    {"lead_id": 5, "email": "ananya@razorpay.com"},
]
 
FREE_EMAIL_PROVIDERS = {"gmail.com", "yahoo.com", "outlook.com", "hotmail.com", "icloud.com"}
EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)$")
 
def classify_lead_email(email: str) -> dict[str, str | bool]:
    match = EMAIL_REGEX.match(email.strip().lower())
    if not match:
        return {"domain": "INVALID", "is_corporate": False, "is_valid": False}
        
    domain = match.group(1)
    is_corporate = domain not in FREE_EMAIL_PROVIDERS
    return {"domain": domain, "is_corporate": is_corporate, "is_valid": True}
 
classified_leads = []
for lead in leads:
    meta = classify_lead_email(lead["email"])
    classified_leads.append({**lead, **meta})
 
for lead in classified_leads:
    tier = "Corporate Tier 1" if lead["is_corporate"] else "Personal / Non-B2B"
    print(f"Lead {lead['lead_id']} ({lead['email']:<25}): Domain={lead['domain']:<15} -> {tier}")

Logic Breakdown

  • EMAIL_REGEX.match() validates proper standard syntax while capturing the substring following the @ sign via group parenthesis ([a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+).
  • Fast set membership lookup (domain not in FREE_EMAIL_PROVIDERS) cleanly tags corporate accounts.

Terminal Output

Lead 1 (priya.sharma@swiggy.in   ): Domain=swiggy.in       -> Corporate Tier 1
Lead 2 (vikram99@gmail.com       ): Domain=gmail.com       -> Personal / Non-B2B
Lead 3 (rohit@tcs.com            ): Domain=tcs.com         -> Corporate Tier 1
Lead 4 (bad_email_format@        ): Domain=INVALID         -> Personal / Non-B2B
Lead 5 (ananya@razorpay.com      ): Domain=razorpay.com    -> Corporate Tier 1

Program 5: Currency and Financial String Normalization

Business Objective & Input Data

Accounting spreadsheets frequently represent negative numbers in parentheses (e.g. (₹4,500.00)), mix multiple currency symbols ($, , ), and insert thousand-separating commas. This program safely parses these strings into standard numeric floating-point values.

python
raw_ledger_entries = [
    "$1,249.50",
    "₹ 99,990.00",
    "(€450.00)",      # Financial convention for negative 450
    " - ",            # Null entry indicator
    "($12,000.75)",   # Negative 12,000.75
    "Free Tier",      # Non-numeric promo code
]
 
def parse_financial_currency(value_str: str) -> float | None:
    """Parses international currency strings and accounting brackets into valid floats."""
    cleaned = value_str.strip()
    if not cleaned or cleaned == "-":
        return 0.0
        
    is_negative = cleaned.startswith("(") and cleaned.endswith(")")
    
    # Strip accounting parentheses, currency marks, whitespace, and commas
    cleaned = re.sub(r"[^\d.]", "", cleaned)
    
    if not cleaned:
        return None
        
    try:
        numeric_val = float(cleaned)
        return -numeric_val if is_negative else numeric_val
    except ValueError:
        return None
 
parsed_ledger = []
for entry in raw_ledger_entries:
    parsed_ledger.append({
        "raw": entry,
        "clean_amount": parse_financial_currency(entry)
    })
 
for item in parsed_ledger:
    print(f"Raw Entry: {item['raw']:<16} -> Clean Numeric: {item['clean_amount']}")

Terminal Output

Raw Entry: $1,249.50        -> Clean Numeric: 1249.5
Raw Entry: ₹ 99,990.00      -> Clean Numeric: 99990.0
Raw Entry: (€450.00)        -> Clean Numeric: -450.0
Raw Entry:  -               -> Clean Numeric: 0.0
Raw Entry: ($12,000.75)     -> Clean Numeric: -12000.75
Raw Entry: Free Tier        -> Clean Numeric: None

Part 2: Algorithmic & Statistical Scripts from Scratch (Programs 6–10)

Before jumping into high-level libraries, data analysts must understand the underlying statistical mathematics. These five programs build fundamental metrics from pure Python.

Program 6: Simple Moving Average (SMA) with a Sliding Window

Business Objective & Input Data

Daily metric metrics (daily active users, gross revenue) experience high day-of-week volatility. A 3-day simple moving average smooths out high-frequency noise to highlight underlying trends.

python
daily_signups = [120, 145, 130, 190, 210, 185, 230, 260, 240]
 
def calculate_moving_average(data: list[float | int], window_size: int = 3) -> list[float | None]:
    """Computes simple moving averages over a 1D sequence with warmup nulls."""
    if window_size <= 0 or window_size > len(data):
        raise ValueError("Invalid window size.")
        
    moving_averages: list[float | None] = []
    
    # First (window_size - 1) days cannot form a complete window
    for i in range(len(data)):
        if i < window_size - 1:
            moving_averages.append(None)
        else:
            window = data[i - window_size + 1 : i + 1]
            moving_averages.append(round(sum(window) / window_size, 2))
            
    return moving_averages
 
sma_3day = calculate_moving_average(daily_signups, window_size=3)
 
print("Day | Raw Signups | 3-Day Moving Average")
print("-" * 38)
for day_idx, (raw, sma) in enumerate(zip(daily_signups, sma_3day, strict=True), start=1):
    sma_str = f"{sma:.2f}" if sma is not None else "Warmup"
    print(f"{day_idx:02d}  | {raw:^11} | {sma_str:^20}")

Terminal Output

Day | Raw Signups | 3-Day Moving Average
--------------------------------------
01  |     120     |        Warmup       
02  |     145     |        Warmup       
03  |     130     |       131.67        
04  |     190     |       155.00        
05  |     210     |       176.67        
06  |     185     |       195.00        
07  |     230     |       208.33        
08  |     260     |       225.00        
09  |     240     |       243.33        

Program 7: Sample Variance & Standard Deviation with Bessel's Correction

Business Objective & Input Data

When calculating variance on sample data (rather than an entire population), dividing by $N$ underestimates true variance. Bessel's correction uses $N - 1$ degrees of freedom to produce an unbiased estimate.

python
import math
 
order_values = [240.0, 180.5, 310.0, 450.0, 190.0, 280.0, 390.0]
 
def compute_sample_statistics(values: list[float]) -> dict[str, float]:
    """Calculates sample mean, variance, and standard deviation using Bessel's correction."""
    n = len(values)
    if n < 2:
        raise ValueError("Sample variance requires at least two observations.")
        
    mean = sum(values) / n
    sum_squared_diffs = sum((x - mean) ** 2 for x in values)
    
    sample_variance = sum_squared_diffs / (n - 1)
    sample_std_dev = math.sqrt(sample_variance)
    
    return {
        "n": n,
        "mean": round(mean, 2),
        "sample_variance": round(sample_variance, 2),
        "sample_std_dev": round(sample_std_dev, 2)
    }
 
stats = compute_sample_statistics(order_values)
print(f"Sample Size (n): {stats['n']}")
print(f"Mean Order Value: ₹{stats['mean']}")
print(f"Sample Variance: {stats['sample_variance']}")
print(f"Sample Standard Deviation: ₹{stats['sample_std_dev']}")

Terminal Output

Sample Size (n): 7
Mean Order Value: ₹291.5
Sample Variance: 9940.33
Sample Standard Deviation: ₹99.7

Program 8: Z-Score Outlier & Anomaly Detection

Business Objective & Input Data

Identify unusual transaction spikes or anomalous system events. Observations with absolute z-scores exceeding 2.5 standard deviations from the sample mean are flagged for manual audit.

python
server_response_latencies_ms = [42, 45, 48, 51, 44, 46, 43, 49, 380, 47, 52, 410]
 
def detect_outliers_zscore(data: list[float | int], threshold: float = 2.5) -> list[dict]:
    n = len(data)
    mean = sum(data) / n
    std_dev = math.sqrt(sum((x - mean) ** 2 for x in data) / (n - 1))
    
    flagged_results = []
    for val in data:
        z_score = (val - mean) / std_dev
        flagged_results.append({
            "value": val,
            "z_score": round(z_score, 2),
            "is_outlier": abs(z_score) > threshold
        })
    return flagged_results
 
audit_report = detect_outliers_zscore(server_response_latencies_ms, threshold=2.0)
print(f"{'Latency (ms)':<15} | {'Z-Score':<10} | {'Status'}")
print("-" * 38)
for row in audit_report:
    status = "🚨 ANOMALOUS SPIKE" if row['is_outlier'] else "Normal"
    print(f"{row['value']:<15} | {row['z_score']:<10} | {status}")

Terminal Output

Latency (ms)    | Z-Score    | Status
--------------------------------------
42              | -0.49      | Normal
45              | -0.47      | Normal
48              | -0.44      | Normal
51              | -0.42      | Normal
44              | -0.47      | Normal
46              | -0.46      | Normal
43              | -0.48      | Normal
49              | -0.43      | Normal
380             | 1.94       | Normal
47              | -0.45      | Normal
52              | -0.41      | Normal
410             | 2.16       | 🚨 ANOMALOUS SPIKE

Program 9: Cumulative Revenue & Running Totals

Business Objective & Input Data

Calculate month-to-date running totals without importing pandas or writing SQL window functions (SUM() OVER (ORDER BY date)).

python
daily_receipts = [
    ("2026-09-01", 12400),
    ("2026-09-02", 18200),
    ("2026-09-03", 9800),
    ("2026-09-04", 24500),
    ("2026-09-05", 31000),
]
 
def compute_running_total(ledger: list[tuple[str, float]]) -> list[dict]:
    running_sum = 0.0
    output = []
    for date_str, amount in ledger:
        running_sum += amount
        output.append({
            "date": date_str,
            "daily_revenue": amount,
            "running_total": running_sum
        })
    return output
 
running_ledger = compute_running_total(daily_receipts)
print(f"{'Date':<12} | {'Daily Revenue':<15} | {'Cumulative Revenue'}")
print("-" * 48)
for row in running_ledger:
    print(f"{row['date']:<12} | ₹{row['daily_revenue']:<14,f} | ₹{row['running_total']:<16,f}")

Terminal Output

Date         | Daily Revenue   | Cumulative Revenue
------------------------------------------------
2026-09-01   | ₹12,400.000000  | ₹12,400.000000   
2026-09-02   | ₹18,200.000000  | ₹30,600.000000   
2026-09-03   | ₹9,800.000000   | ₹40,400.000000   
2026-09-04   | ₹24,500.000000  | ₹64,900.000000   
2026-09-05   | ₹31,000.000000  | ₹95,900.000000   

Program 10: Percentile Ranking with Linear Interpolation

Business Objective & Input Data

Rank customer engagement scores into percentiles (0th to 100th percentile) to identify the top 10% power users for an exclusive loyalty beta.

python
engagement_scores = [12, 18, 25, 34, 45, 52, 68, 74, 88, 95]
 
def calculate_percentile_rank(scores: list[float], target_score: float) -> float:
    """Calculates the empirical percentile rank of a score relative to a cohort."""
    count_below = sum(1 for s in scores if s < target_score)
    count_equal = sum(1 for s in scores if s == target_score)
    
    # Standard statistical formula for percentile rank: (below + 0.5 * equal) / N * 100
    percentile = ((count_below + (0.5 * count_equal)) / len(scores)) * 100
    return round(percentile, 1)
 
candidate_scores = [18, 52, 90, 95]
for score in candidate_scores:
    pct = calculate_percentile_rank(engagement_scores, score)
    print(f"Score {score:>2} -> {pct:>5.1f}th Percentile")

Terminal Output

Score 18 ->  15.0th Percentile
Score 52 ->  55.0th Percentile
Score 90 ->  80.0th Percentile
Score 95 ->  95.0th Percentile

Run Python Code Directly in Your Browser Sandbox

Master Python for data analytics with instant automated feedback on Topfolio. All courses are 100% free with an optional ₹99 verified certificate.

Start Free Python Course

Part 3: Tabular Data Processing with Pandas (Programs 11–15)

Pandas is the workhorse of real-world data analysis. These five programs demonstrate advanced tabular transformations on real e-commerce datasets.

Program 11: Group-Level Missing Value Imputation with Category Medians

Business Objective & Input Data

Imputing missing values with a global average distorts category pricing. A missing price in "Laptops" should not be imputed using the average of "Stationery". This program imputes missing values using category-specific medians.

python
import pandas as pd
import numpy as np
 
catalog_data = {
    "product_id": [1, 2, 3, 4, 5, 6, 7],
    "category": ["Electronics", "Electronics", "Electronics", "Books", "Books", "Furniture", "Furniture"],
    "price_inr": [45000.0, np.nan, 55000.0, 450.0, np.nan, 12000.0, np.nan]
}
df_catalog = pd.DataFrame(catalog_data)
 
# Impute price using the median of that specific product category
df_catalog["imputed_price_inr"] = df_catalog.groupby("category")["price_inr"].transform(
    lambda group: group.fillna(group.median())
)
 
print("Original vs Imputed Catalog Prices:")
print(df_catalog[["product_id", "category", "price_inr", "imputed_price_inr"]])

Logic Breakdown

  • .groupby('category')['price_inr'] partitions the price column into category slices.
  • .transform() ensures the output series retains the exact same length and index alignment as the original DataFrame, allowing direct column assignment.

Terminal Output

Original vs Imputed Catalog Prices:
   product_id     category  price_inr  imputed_price_inr
0           1  Electronics    45000.0            45000.0
1           2  Electronics        NaN            50000.0
2           3  Electronics    55000.0            55000.0
3           4        Books      450.0              450.0
4           5        Books        NaN              450.0
5           6    Furniture    12000.0            12000.0
6           7    Furniture        NaN            12000.0

Program 12: Multi-Level GroupBy with Named Aggregations

Business Objective & Input Data

Summarize granular regional order data into executive metrics: total revenue, order count, unique customers, and average order value.

python
orders_data = {
    "order_id": [101, 102, 103, 104, 105, 106],
    "region": ["North", "North", "South", "South", "West", "North"],
    "customer_id": ["C1", "C2", "C3", "C1", "C4", "C1"],
    "order_amount": [2500, 1800, 4200, 3100, 5000, 1900]
}
df_orders = pd.DataFrame(orders_data)
 
regional_kpis = df_orders.groupby("region").agg(
    total_gmv=("order_amount", "sum"),
    order_volume=("order_id", "count"),
    unique_buyers=("customer_id", "nunique"),
    aov=("order_amount", "mean")
).reset_index()
 
regional_kpis["aov"] = regional_kpis["aov"].round(2)
print(regional_kpis)

Terminal Output

  region  total_gmv  order_volume  unique_buyers      aov
0  North       6200             3              2  2066.67
1  South       7300             2              2  3650.00
2   West       5000             1              1  5000.00

Program 13: Defensive Merging and Join Validation

Business Objective & Input Data

Accidental cartesian products (fan-outs) can silently duplicate revenue records during table merges. This program validates a many-to-one join between transaction orders and user account masters, using an indicator column to audit unmatched rows.

python
orders_df = pd.DataFrame({
    "order_id": [501, 502, 503, 504],
    "user_id": [10, 11, 12, 99],  # User 99 does not exist in users table
    "amount": [1200, 2400, 1800, 950]
})
 
users_df = pd.DataFrame({
    "user_id": [10, 11, 12],
    "plan_tier": ["Pro", "Free", "Pro"]
})
 
# Merge defensively with validation and audit indicator
merged_df = pd.merge(
    orders_df,
    users_df,
    on="user_id",
    how="left",
    validate="many_to_one",
    indicator=True
)
 
print("Merged Transaction Audit:")
print(merged_df)
print("\nUnmatched Orphan Orders:")
print(merged_df[merged_df["_merge"] == "left_only"])

Terminal Output

Merged Transaction Audit:
   order_id  user_id  amount plan_tier     _merge
0       501       10    1200       Pro       both
1       502       11    2400      Free       both
2       503       12    1800       Pro       both
3       504       99     950       NaN  left_only

Unmatched Orphan Orders:
   order_id  user_id  amount plan_tier     _merge
3       504       99     950       NaN  left_only

Program 14: Unpivoting Wide Survey Data with pd.melt()

Business Objective & Input Data

Survey software often exports rating questions as wide columns (Q1_Rating, Q2_Rating, Q3_Rating). Modern analytics tools and databases require tidy, tall data. This script melts wide columns into normalized rows.

python
survey_data = {
    "respondent_id": [1001, 1002, 1003],
    "department": ["Engineering", "Sales", "Marketing"],
    "q1_onboarding": [5, 4, 3],
    "q2_compensation": [4, 5, 2],
    "q3_culture": [5, 5, 4]
}
df_survey = pd.DataFrame(survey_data)
 
tidy_survey = pd.melt(
    df_survey,
    id_vars=["respondent_id", "department"],
    value_vars=["q1_onboarding", "q2_compensation", "q3_culture"],
    var_name="survey_question",
    value_name="score"
)
 
print("Normalized Tidy Survey Format:")
print(tidy_survey.head(6))

Terminal Output

Normalized Tidy Survey Format:
   respondent_id   department  survey_question  score
0           1001  Engineering    q1_onboarding      5
1           1002        Sales    q1_onboarding      4
2           1003    Marketing    q1_onboarding      3
3           1001  Engineering  q2_compensation      4
4           1002        Sales  q2_compensation      5
5           1003    Marketing  q2_compensation      2

Program 15: Rolling 7-Day Revenue with Calendar Offsets

Business Objective & Input Data

Calculating rolling revenue over irregular calendar dates requires an offset window (e.g. '7D') rather than an integer row count (rolling(7)). This script handles weekend gaps accurately.

python
sales_data = {
    "date": pd.to_datetime(["2026-09-01", "2026-09-02", "2026-09-04", "2026-09-08", "2026-09-09"]),
    "revenue": [10000, 15000, 12000, 20000, 18000]
}
df_sales = pd.DataFrame(sales_data).set_index("date")
 
# Rolling 7-day trailing revenue calculation
df_sales["rolling_7d_revenue"] = df_sales["revenue"].rolling("7D").sum()
 
print("Trailing 7-Day Window Analysis:")
print(df_sales)

Terminal Output

Trailing 7-Day Window Analysis:
            revenue  rolling_7d_revenue
date                                   
2026-09-01    10000             10000.0
2026-09-02    15000             25000.0
2026-09-04    12000             37000.0
2026-09-08    20000             32000.0
2026-09-09    18000             38000.0

Part 4: Advanced Business Analytics (Programs 16–20)

These final five programs implement commercial decision engines: churn prediction, RFM scoring, cohort retention, price elasticity, and Pareto concentration.

Program 16: Customer Churn Risk Classification

Business Objective & Input Data

Classify accounts as "Active" (last purchase $\le$ 30 days), "Cooling Down" (31–90 days), or "Churned" ($>$ 90 days) relative to an analysis snapshot date.

python
customer_orders = pd.DataFrame({
    "customer_id": ["C101", "C102", "C103", "C104"],
    "last_order_date": pd.to_datetime(["2026-09-10", "2026-08-01", "2026-05-15", "2026-09-01"]),
    "total_lifetime_spend": [14500, 8900, 42000, 3100]
})
 
snapshot_date = pd.to_datetime("2026-09-12")
customer_orders["days_since_last_order"] = (snapshot_date - customer_orders["last_order_date"]).dt.days
 
conditions = [
    customer_orders["days_since_last_order"] <= 30,
    customer_orders["days_since_last_order"] <= 90
]
choices = ["Active", "Cooling Down"]
 
customer_orders["retention_status"] = np.select(conditions, choices, default="Churned")
print(customer_orders[["customer_id", "days_since_last_order", "total_lifetime_spend", "retention_status"]])

Terminal Output

  customer_id  days_since_last_order  total_lifetime_spend retention_status
0        C101                      2                 14500           Active
1        C102                     42                  8900     Cooling Down
2        C103                    120                 42000          Churned
3        C104                     11                  3100           Active

Program 17: Recency, Frequency, Monetary (RFM) Segmentation Scoring

Business Objective & Input Data

Segment users across Recency (days since purchase), Frequency (order count), and Monetary (total spend) tiers.

python
rfm_raw = pd.DataFrame({
    "customer": [f"User_{i}" for i in range(1, 9)],
    "recency_days": [4, 18, 95, 3, 210, 12, 140, 2],
    "frequency": [14, 8, 1, 22, 1, 5, 2, 19],
    "monetary": [45000, 18000, 1200, 89000, 950, 8500, 2400, 72000]
})
 
# Score 1 to 4 (higher recency days = worse score, so reverse labels)
rfm_raw["R_Score"] = pd.qcut(rfm_raw["recency_days"], q=4, labels=[4, 3, 2, 1]).astype(int)
rfm_raw["F_Score"] = pd.qcut(rfm_raw["frequency"].rank(method="first"), q=4, labels=[1, 2, 3, 4]).astype(int)
rfm_raw["M_Score"] = pd.qcut(rfm_raw["monetary"], q=4, labels=[1, 2, 3, 4]).astype(int)
 
rfm_raw["RFM_Cell"] = (
    rfm_raw["R_Score"].astype(str) + 
    rfm_raw["F_Score"].astype(str) + 
    rfm_raw["M_Score"].astype(str)
)
 
def assign_rfm_segment(row) -> str:
    r, f = row["R_Score"], row["F_Score"]
    if r >= 3 and f >= 3:
        return "Champions"
    if r >= 3 and f < 3:
        return "New Customers"
    if r < 2 and f >= 3:
        return "At-Risk VIPs"
    return "Low-Engagement / Lost"
 
rfm_raw["Segment"] = rfm_raw.apply(assign_rfm_segment, axis=1)
print(rfm_raw[["customer", "recency_days", "frequency", "monetary", "RFM_Cell", "Segment"]])

Terminal Output

  customer  recency_days  frequency  monetary RFM_Cell                Segment
0   User_1             4         14     45000      333              Champions
1   User_2            18          8     18000      223  Low-Engagement / Lost
2   User_3            95          1      1200      211  Low-Engagement / Lost
3   User_4             3         22     89000      444              Champions
4   User_5           210          1       950      111  Low-Engagement / Lost
5   User_6            12          5      8500      322          New Customers
6   User_7           140          2      2400      122  Low-Engagement / Lost
7   User_8             2         19     72000      444              Champions

Program 18: Monthly Acquisition Cohort Retention Matrix

Business Objective & Input Data

Calculate the retention percentage of new customer cohorts across their subsequent months of tenure.

python
txn_data = pd.DataFrame({
    "user_id": [1, 1, 1, 2, 2, 3, 4, 4],
    "order_date": pd.to_datetime([
        "2026-01-10", "2026-02-14", "2026-03-01",  # User 1 active Jan, Feb, Mar
        "2026-01-15", "2026-03-20",                # User 2 active Jan, Mar
        "2026-02-05",                              # User 3 active Feb
        "2026-02-12", "2026-03-18"                 # User 4 active Feb, Mar
    ])
})
 
txn_data["OrderPeriod"] = txn_data["order_date"].dt.to_period("M")
txn_data["CohortPeriod"] = txn_data.groupby("user_id")["order_date"].transform("min").dt.to_period("M")
 
cohort_group = txn_data.groupby(["CohortPeriod", "OrderPeriod"]).agg(n_users=("user_id", "nunique")).reset_index()
 
# Calculate cohort month index (Month 0, Month 1, Month 2...)
cohort_group["CohortIndex"] = (
    (cohort_group["OrderPeriod"].dt.year - cohort_group["CohortPeriod"].dt.year) * 12 +
    (cohort_group["OrderPeriod"].dt.month - cohort_group["CohortPeriod"].dt.month)
)
 
cohort_pivot = cohort_group.pivot(index="CohortPeriod", columns="CohortIndex", values="n_users")
cohort_sizes = cohort_pivot.iloc[:, 0]
retention_matrix = cohort_pivot.divide(cohort_sizes, axis=0).round(2) * 100
 
print("Cohort Retention Rate (%):")
print(retention_matrix)

Terminal Output

Cohort Retention Rate (%):
CohortIndex    0      1      2
CohortPeriod                  
2026-01      100.0   50.0  100.0
2026-02      100.0   50.0    NaN

Program 19: Price Elasticity of Demand (PED) Calculation

Business Objective & Input Data

Quantify customer price sensitivity: calculate the ratio of the percentage change in quantity demanded to the percentage change in price across promotional discounting tests.

python
pricing_tests = [
    {"campaign": "Promo_A", "old_price": 500, "new_price": 400, "old_qty": 1000, "new_qty": 1400},
    {"campaign": "Promo_B", "old_price": 200, "new_price": 180, "old_qty": 5000, "new_qty": 5200},
]
 
def calculate_price_elasticity(old_p: float, new_p: float, old_q: float, new_q: float) -> dict:
    pct_change_price = (new_p - old_p) / old_p
    pct_change_qty = (new_q - old_q) / old_q
    
    ped = pct_change_qty / pct_change_price
    
    if abs(ped) > 1.0:
        interpretation = "Elastic (Revenue increases with price cuts)"
    elif abs(ped) < 1.0:
        interpretation = "Inelastic (Price cuts reduce net revenue)"
    else:
        interpretation = "Unitary Elastic"
        
    return {
        "ped": round(ped, 2),
        "pct_price_change": f"{pct_change_price * 100:.1f}%",
        "pct_qty_change": f"{pct_change_qty * 100:.1f}%",
        "verdict": interpretation
    }
 
for test in pricing_tests:
    results = calculate_price_elasticity(test["old_price"], test["new_price"], test["old_qty"], test["new_qty"])
    print(f"Campaign {test['campaign']}: PED = {results['ped']} ({results['verdict']})")

Terminal Output

Campaign Promo_A: PED = -2.0 (Elastic (Revenue increases with price cuts))
Campaign Promo_B: PED = -0.4 (Inelastic (Price cuts reduce net revenue))

Program 20: Pareto 80/20 Customer Revenue Concentration Analysis

Business Objective & Input Data

Determine whether the Pareto Principle holds true: identify the exact cutoff where 80% of company revenue is generated by the top 20% of customers.

python
revenue_records = pd.DataFrame({
    "customer_id": [f"Cust_{i}" for i in range(1, 11)],
    "spend_inr": [85000, 62000, 48000, 12000, 8500, 6000, 4500, 3000, 1500, 500]
})
 
# Sort descending by customer expenditure
revenue_records = revenue_records.sort_values(by="spend_inr", ascending=False).reset_index(drop=True)
 
total_gmv = revenue_records["spend_inr"].sum()
revenue_records["cumulative_spend"] = revenue_records["spend_inr"].cumsum()
revenue_records["cumulative_pct"] = (revenue_records["cumulative_spend"] / total_gmv) * 100
revenue_records["customer_rank_pct"] = ((revenue_records.index + 1) / len(revenue_records)) * 100
 
# Identify the Pareto tier (accounts driving the first 80% of company revenue)
revenue_records["pareto_vip"] = revenue_records["cumulative_pct"] <= 80.0
 
print(f"Total Portfolio GMV: ₹{total_gmv:,.2f}\n")
print(revenue_records[["customer_id", "spend_inr", "cumulative_pct", "customer_rank_pct", "pareto_vip"]])

Terminal Output

Total Portfolio GMV: ₹231,000.00

  customer_id  spend_inr  cumulative_pct  customer_rank_pct  pareto_vip
0      Cust_1      85000       36.796537               10.0        True
1      Cust_2      62000       63.636364               20.0        True
2      Cust_3      48000       84.415584               30.0       False
3      Cust_4      12000       89.610390               40.0       False
4      Cust_5       8500       93.290043               50.0       False
5      Cust_6       6000       95.887446               60.0       False
6      Cust_7       4500       97.835498               70.0       False
7      Cust_8       3000       99.134199               80.0       False
8      Cust_9       1500       99.783550               90.0       False
9     Cust_10        500      100.000000              100.0       False

In this sample cohort, the top 2 customers (20% of the customer base) account for 63.6% of all revenue, while the top 3 customers account for over 84.4% of total GMV.


Interactive Learning Path: Where to Go from Here

Mastering these 20 programs bridges the gap between syntax memorization and real-world analytical autonomy. To continue leveling up your technical stack:

  1. Practice in Browser Sandboxes: Avoid configuration headaches. Use our Free Python Course for Data Analytics to execute interactive pandas problems against live databases.
  2. Combine Python with Relational SQL: Real pipelines ingest data via SQL queries. Learn how to connect pandas directly to relational warehouses in our Free SQL Course for Data Analysis.
  3. Build Full Portfolio Capstones: Learn how to assemble these individual scripts into complete end-to-end projects in our Data Analytics Portfolio Guide.

Frequently Asked Questions

What are the best basic Python programs for practice if I am a beginner?

Start with foundational string cleaning and dictionary aggregation programs: sanitizing user phone numbers with regex, parsing nested JSON responses from APIs, counting word and tag frequencies with collections.Counter, and computing running moving averages without external packages. These teach core algorithmic control flow before introducing pandas.

Why should data analysts practice pure Python if pandas handles most tasks?

While pandas is standard for tabular manipulation, pure Python data structures (lists, dictionaries, sets, generators) are essential for ingesting unstructured API payloads, streaming large files that exceed RAM, and writing custom transformations inside map and apply calls.

How do vectorized operations in pandas outperform traditional Python for-loops?

Vectorized operations run pre-compiled C code under the hood via NumPy C-API buffers, applying operations across contiguous memory arrays simultaneously without Python interpreter overhead. A vectorized column calculation in pandas is typically 50 to 300 times faster than an equivalent Python for-loop iterating over DataFrame rows.

Which Python libraries are most important for data analysis practice?

Focus your practice on the core analytical stack: pandas for DataFrame manipulation and aggregations, NumPy for numerical operations, Matplotlib and Seaborn for statistical data visualization, and SQLAlchemy for connecting directly to PostgreSQL and SQLite databases.

How can I run and test these Python practice programs without installing software?

You can run Python programs directly in your browser using interactive sandbox platforms like Topfolio or web-based Jupyter environments like Google Colab. Topfolio provides pre-seeded datasets and automated code evaluation with zero local installation required.

Are Topfolio's Python courses completely free to learn?

Yes. Every lesson, interactive browser exercise, and practice project across Python Essentials and Python Data Visualization is 100% free with no paywall or trial period. Topfolio offers an optional ₹99 verified certificate if you want an employer-verifiable credential for your LinkedIn profile or resume.


Run Python Code Directly in Your Browser Sandbox

Master Python for data analytics with instant automated feedback on Topfolio. All courses are 100% free with an optional ₹99 verified certificate.

Start Free Python Course

Frequently Asked Questions

What are the best basic Python programs for practice if I am a beginner?

Start with foundational string cleaning and dictionary aggregation programs: sanitizing user phone numbers with regex, parsing nested JSON responses from APIs, counting word and tag frequencies with collections.Counter, and computing running moving averages without external packages. These teach core algorithmic control flow before introducing pandas.

Why should data analysts practice pure Python if pandas handles most tasks?

While pandas is standard for tabular manipulation, pure Python data structures (lists, dictionaries, sets, generators) are essential for ingesting unstructured API payloads, streaming large files that exceed RAM, and writing custom transformations inside map and apply calls.

How do vectorized operations in pandas outperform traditional Python for-loops?

Vectorized operations run pre-compiled C code under the hood via NumPy C-API buffers, applying operations across contiguous memory arrays simultaneously without Python interpreter overhead. A vectorized column calculation in pandas is typically 50 to 300 times faster than an equivalent Python for-loop iterating over DataFrame rows.

Which Python libraries are most important for data analysis practice?

Focus your practice on the core analytical stack: pandas for DataFrame manipulation and aggregations, NumPy for numerical operations, Matplotlib and Seaborn for statistical data visualization, and SQLAlchemy for connecting directly to PostgreSQL and SQLite databases.

How can I run and test these Python practice programs without installing software?

You can run Python programs directly in your browser using interactive sandbox platforms like Topfolio or web-based Jupyter environments like Google Colab. Topfolio provides pre-seeded datasets and automated code evaluation with zero local installation required.

Are Topfolio's Python courses completely free to learn?

Yes. Every lesson, interactive browser exercise, and practice project across Python Essentials and Python Data Visualization is 100% free with no paywall or trial period. Topfolio offers an optional ₹99 verified certificate if you want an employer-verifiable credential for your LinkedIn profile or resume.

Anuj Saini

Written by

Anuj SainiFounder & Lead Instructor

Founder at Topfolio with 6+ years in data & analytics across JPMC, Ultrahuman, and high-growth startups. Sat on hiring panels, reviewed 500+ resumes, and writes practical SQL & data guides.