Data Cleaning and Feature Engineering
Apply the advanced cleaning checklist, engineer date features and ratio columns, and validate the clean dataset with assertions.
Data Cleaning and Feature Engineering is a free Pandas & NumPy Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Pandas & NumPy Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Loading the Audited Dataset
Continuing the capstone project, load the clean Parquet checkpoint saved in the previous step. This decouples the cleaning stage from ingestion — you can iterate on cleaning logic without re-running the slow merge and parse operations. Read the clean_orders Parquet and verify the row count and dtypes match expectations from the audit summary. The Parquet file preserves all dtypes including categorical columns, so no re-casting is needed.
import pandas as pd
# Load from checkpoint
df = pd.read_parquet('output/clean_orders.parquet')
print(f'Loaded: {df.shape}')
print(df.dtypes)
print(f'Date range: {df["order_date"].min().date()} to {df["order_date"].max().date()}')Applying the Advanced Cleaning Checklist
With the merged dataset loaded, apply the advanced cleaning checklist: remove exact and partial duplicates on order_id, cap outliers in unit_price using IQR fencing, standardise inconsistent category values (e.g., 'Electronics' vs 'electronics' vs 'ELECTRONIC'), and validate that quantity × unit_price is always positive for normal orders. Each step is a function that accepts and returns a DataFrame, making the pipeline testable and reversible.
import pandas as pd
import numpy as np
def remove_duplicates(df):
n_before = len(df)
df = df.drop_duplicates(subset=['order_id'], keep='first')
print(f'Duplicates removed: {n_before - len(df)}')
return df
def standardise_categories(df):
df['category'] = (df['category']
.astype(str)
.str.strip()
.str.title())
return df
def cap_price_outliers(df):
q1, q3 = df['unit_price'].quantile([0.25, 0.75])
iqr = q3 - q1
upper = q3 + 3 * iqr
df['unit_price'] = df['unit_price'].clip(upper=upper)
return df
df = remove_duplicates(df)
df = standardise_categories(df)
df = cap_price_outliers(df)
print('Cleaning complete.')Engineering Date Features
Extract temporal features from the order date using the .dt accessor. Create year, month, quarter, day_of_week, and week_of_year columns. These features power groupby analyses (monthly revenue), time-based filters (Q3 only), and visualisations. Also create a year_month string column (e.g., '2024-06') as a human-readable period label for chart axes.
import pandas as pd
df['year'] = df['order_date'].dt.year
df['month'] = df['order_date'].dt.month
df['quarter'] = df['order_date'].dt.quarter
df['day_of_week'] = df['order_date'].dt.dayofweek # 0=Monday
df['week'] = df['order_date'].dt.isocalendar().week.astype('int32')
df['year_month'] = df['order_date'].dt.to_period('M').astype(str)
print('Date features created:')
print(df[['order_date', 'year', 'month', 'quarter', 'year_month']].head())Computing Line-Item Revenue
The most fundamental feature in a sales dataset is line-item revenue: revenue = quantity × unit_price. Create this as a new column. Also create a gross_margin column if cost data is available: margin = (price - cost) / price. These derived columns are the building blocks of all revenue KPIs. Always use vectorised column operations rather than loops — a single assignment on the full column is orders of magnitude faster than iterating row by row.
import pandas as pd
# Line-item revenue
df['revenue'] = (df['quantity'] * df['unit_price']).astype('float32')
# Gross margin (if unit_cost column exists)
if 'unit_cost' in df.columns:
df['gross_margin'] = (df['unit_price'] - df['unit_cost']) / df['unit_price']
# Discount flag: orders with more than 20% off list price
if 'list_price' in df.columns:
df['is_discounted'] = df['unit_price'] < df['list_price'] * 0.8
print('Revenue stats:')
print(df['revenue'].describe().round(2))Customer Cohort Assignment
A cohort is a group of customers who share a characteristic — typically the time period they first made a purchase. Assign each customer to their acquisition cohort by finding their first order date. Use groupby('customer_id')['order_date'].min() to compute the first order date per customer, then create a cohort_month column by converting it to a year-month period. This cohort label is the foundation of the retention matrix in the next lesson.
import pandas as pd
# First purchase date per customer
first_purchase = (
df.groupby('customer_id')['order_date']
.min()
.dt.to_period('M')
.astype(str)
.rename('cohort_month')
)
# Merge back onto orders
df = df.merge(first_purchase, on='customer_id', how='left')
print('Cohort distribution (top 5):')
print(df['cohort_month'].value_counts().head())
print(f'Distinct cohorts: {df["cohort_month"].nunique()}')Customer Lifetime Value Features
Compute customer-level summary features: total orders, total revenue, average order value, days since first/last purchase, and number of distinct categories purchased. Merge these back onto the main DataFrame as customer-level enrichment columns. These features are used for segmentation (e.g., high-value vs. low-value customers) and as inputs to machine learning models predicting churn or upsell probability.
import pandas as pd
customer_stats = df.groupby('customer_id').agg(
total_orders=('order_id', 'nunique'),
total_revenue=('revenue', 'sum'),
avg_order_value=('revenue', 'mean'),
categories_purchased=('category', 'nunique'),
first_order=('order_date', 'min'),
last_order=('order_date', 'max')
).reset_index()
customer_stats['days_as_customer'] = (
(customer_stats['last_order'] - customer_stats['first_order'])
.dt.days
)
print(customer_stats.describe().round(2))Schema Validation with Assertions
After engineering features, run schema validation assertions to confirm the data meets expectations before passing it to the analysis stage. Check that all expected columns exist, revenue is non-negative for normal orders, cohort_month is non-null, and year_month matches the analysis year. These assertions act as a contract: if any fails, the pipeline stops immediately with a clear error message rather than silently producing wrong results.
import pandas as pd
def validate_clean_df(df):
required_cols = ['order_id', 'customer_id', 'revenue', 'year_month',
'cohort_month', 'category', 'region', 'order_date']
missing = [c for c in required_cols if c not in df.columns]
assert not missing, f'Missing columns: {missing}'
assert (df['revenue'] >= 0).all(), 'Negative revenue found'
assert df['cohort_month'].notna().all(), 'Null cohort_month values'
assert df['order_date'].notna().all(), 'Null order dates'
print(f'Validation passed: {len(df):,} rows, {len(df.columns)} columns')
validate_clean_df(df)Handling Low-Frequency Categories
In real datasets, some categories or regions appear only a handful of times — too few for meaningful analysis. Replace low-frequency values with 'Other' to avoid charts cluttered with dozens of single-order categories. A practical threshold: values appearing in fewer than 0.5% of rows are collapsed. This is also important for machine learning: one-hot encoding 200 rare categories wastes memory and adds noise.
import pandas as pd
def collapse_rare_values(df, col, min_pct=0.005):
threshold = len(df) * min_pct
counts = df[col].value_counts()
rare = counts[counts < threshold].index
n_rare = len(rare)
df[col] = df[col].apply(lambda x: 'Other' if x in rare else x)
print(f'{col}: collapsed {n_rare} rare values into "Other"')
return df
df = collapse_rare_values(df, 'category', min_pct=0.005)
df = collapse_rare_values(df, 'region', min_pct=0.005)
print('Category distribution after collapsing:')
print(df['category'].value_counts())Saving the Feature-Engineered Dataset
Save the fully cleaned and feature-engineered DataFrame as a Parquet checkpoint. This is the analysis-ready dataset — the output of all ingestion, cleaning, and feature engineering steps. Subsequent pipeline stages (KPI computation, visualisation, reporting) read from this checkpoint. The checkpoint also serves as a deliverable: it can be shared with team members or uploaded to a data lake for others to query with SQL or Pandas.
import pandas as pd
# Convert category columns back to Categorical for memory efficiency
for col in ['category', 'region', 'acquisition_channel']:
if col in df.columns:
df[col] = df[col].astype('category')
# Save analysis-ready dataset
df.to_parquet('output/analysis_ready.parquet', index=False)
# Also save customer stats for segmentation
customer_stats.to_parquet('output/customer_stats.parquet', index=False)
print(f'Analysis-ready dataset: {df.shape}')
print(f'Memory usage: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')Feature Engineering Summary
Good feature engineering balances adding predictive signal with keeping the pipeline interpretable and testable. The features created in this stage — revenue, date parts, cohort_month, customer stats, collapsed categories — were all driven by the analysis goals defined in the project setup. Resist the temptation to create dozens of features speculatively. For each feature, ask: will this be used in at least one KPI or visualisation? If not, defer it. Keep the analysis-ready dataset lean and purposeful.
import pandas as pd
# Summary of engineered features
df = pd.read_parquet('output/analysis_ready.parquet')
original_cols = ['order_id', 'customer_id', 'order_date', 'quantity', 'unit_price']
engineered_cols = [c for c in df.columns if c not in original_cols]
print('Original columns:', original_cols)
print('Engineered features:', engineered_cols)
print(f'Total columns: {len(df.columns)}')
print(f'Total rows: {len(df):,}')Documenting Decisions in Code
For every non-obvious cleaning or engineering decision, add a comment explaining the why. Future you (or a team member) will not remember why you set the IQR multiplier to 3 instead of 1.5, or why you used 0.5% as the rare-category threshold. Inline comments and a decisions log (a simple list in the project README or a markdown file) make the pipeline maintainable and auditable. Clean, documented code is the true deliverable — the output files are just its consequence.
# Engineering decisions log
DECISIONS = '''
# Data Cleaning & Feature Engineering Decisions
## Duplicate handling
Kept first occurrence of duplicate order_id (most likely the original order).
## Outlier capping
Unit price capped at Q3 + 3*IQR (not 1.5*IQR) because price distribution
has legitimate high-value enterprise orders that should not be removed.
## Rare category threshold: 0.5%
Values below 0.5% of total orders collapsed to "Other" to keep charts readable
and avoid spurious significance in category-level tests.
## Cohort assignment
Cohort = first purchase calendar month (not signup month), because many users
sign up but do not purchase until months later.
'''
print(DECISIONS)Quick Check
Test your understanding of Data Analysis concepts from this lesson.
Lesson Recap
In this lesson you learned: modular cleaning functions (remove duplicates, cap outliers, standardise categories) each accept and return a DataFrame for easy testing, feature engineering created date parts, line-item revenue, customer cohorts, and summary statistics, and schema validation assertions guard the transition from cleaning to analysis. Next up we compute the project's core KPIs: monthly cohort retention, product revenue, and rolling active users.
Frequently asked questions
Is the “Data Cleaning and Feature Engineering” lesson free?
Yes — the full text of “Data Cleaning and Feature Engineering” is free to read here on the web, and the Pandas & NumPy Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Pandas & NumPy Academy course, upgrade to CoddyKit PRO.
What will I learn in “Data Cleaning and Feature Engineering”?
Apply the advanced cleaning checklist, engineer date features and ratio columns, and validate the clean dataset with assertions. You practise Pandas & NumPy Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Pandas & NumPy Academy?
No prior experience is required. Pandas & NumPy Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Data Cleaning and Feature Engineering” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Pandas & NumPy Academy lesson?
Yes. Every Pandas & NumPy Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Project Setup and Data Ingestion
- Data Cleaning and Feature Engineering
- Analysis and KPI Computation
- Final Visualisation and Report Export