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.
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:
- The Business Objective & Sample Input Data
- Production-Ready Python 3 Code (commented and runnable)
- Line-by-Line Logic Breakdown
- 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:
- 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.
- Use Vectorized Pandas for Tabular Math: Once data is structured into rows and columns, avoid Python
forloops. 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).
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.
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 toTrue, 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.
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_keysis incremented toduplicate_countand 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.
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.
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.
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.
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.
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)).
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.
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 CoursePart 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
- Practice in Browser Sandboxes: Avoid configuration headaches. Use our Free Python Course for Data Analytics to execute interactive pandas problems against live databases.
- 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.
- 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 CourseFrequently 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.

Written by
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.
Related Articles
Customer Analytics in Python: Cohort Analysis, RFM Segmentation & LTV
Master customer analytics in Python with Pandas and Seaborn. Calculate RFM scores, build customer segments, run monthly cohort retention heatmaps, and estimate Customer Lifetime Value (LTV).
Market Basket Analysis in Python: Support, Confidence, Lift & Apriori Explained
Master Market Basket Analysis and Association Rule Mining in Python. Learn the mathematical intuition behind Support, Confidence, and Lift, one-hot encode transaction baskets, and implement Apriori and FP-Growth using Pandas and Mlxtend.
REST APIs for Data Analysts in Python: Authentication, Pagination & JSON Normalization
Master REST API data extraction in Python. Learn how to handle Bearer tokens and API keys, loop through offset and cursor pagination, flatten nested JSON with pd.json_normalize(), and build fault-tolerant pipelines with automatic retries.