Data Preparation: Explore & Clean
Two-phase workflow for systematic data preparation. Always run EDA first — findings drive the cleaning strategy.
Available scripts
| Script |
Usage |
| eda.py |
uv run ${CLAUDE_SKILL_DIR}/scripts/eda.py data.csv --target price |
| clean.py |
uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv -o clean.csv |
| engineer_features.py |
uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv -o data/features.csv |
Phase 1: Exploratory Data Analysis
Quick start
uv run ${CLAUDE_SKILL_DIR}/scripts/eda.py $ARGUMENTS
uv run ${CLAUDE_SKILL_DIR}/scripts/eda.py data/train.csv --target price
The eda.py script runs all 9 checks below in order and prints a complete report.
Column Classification
Before profiling, classify each column:
| Type |
Description |
Examples |
| Identifier |
Unique keys, foreign keys |
user_id, order_id, session_id |
| Dimension |
Categorical attributes for grouping |
status, region, category, plan_type |
| Metric |
Quantitative values for measurement |
revenue, count, duration, score |
| Temporal |
Dates and timestamps |
created_at, event_date, updated_at |
| Text |
Free-form text fields |
description, notes, comment |
| Boolean |
True/false flags |
is_active, has_subscription |
This classification drives which profiling checks to run on each column.
EDA Pipeline Steps
- Overview — shape, memory, types, first rows
- Missing values — count and percentage per column, sorted by severity
- Numeric features — describe stats, zero-heavy warnings, negative value notes
- Categorical features — unique counts, top values, high cardinality, ID detection
- Correlations — pairs with |r| > 0.7 flagged for multicollinearity
- Target analysis — classification vs regression, imbalance check (with
--target)
- Duplicates — exact duplicate row count and percentage
- Completeness scoring — GREEN (>99%) / YELLOW (95-99%) / ORANGE (80-95%) / RED (<80%)
- Accuracy red flags — placeholder values (0, -1, 999), round number bias, text placeholders
EDA report format
=== EDA Report: {filename} ===
Overview: 50,000 rows x 25 columns (15 numeric, 8 categorical, 2 datetime)
Data quality:
- Missing: col_a (12%), col_b (8%), col_c (6%)
- Duplicates: 42 rows (0.08%)
- High cardinality: user_id (unique per row)
Key findings:
1. feature_x and feature_y correlated (r=0.89) — multicollinearity
2. Target imbalanced: 92% class 0, 8% class 1
3. price has 500 zeros — possibly coded missing values
4. date_created spans 2019-2024 — check for temporal leakage
Recommendations:
1. Drop user_id (ID column, no predictive value)
2. Handle class imbalance (SMOTE, class weights, stratified sampling)
3. Investigate zero prices
4. Use feature_x OR feature_y, not both
Red flags to always check
- Columns that are 100% null
- Numeric columns stored as strings
- ID columns that could leak information
- Future data in features (temporal leakage)
- Constant columns (zero variance)
- Extreme class imbalance (>10:1)
- Placeholder values (0, -1, 999, 9999, "N/A", "TBD", "test")
- Default value dominance (one value has suspiciously high frequency)
- Round number bias (all values multiples of 5 or 10 — suggests estimation)
- Stale data (updated_at shows no recent changes in an active system)
Phase 2: Data Cleaning
Quick start
# Full cleaning pipeline
uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv -o clean.csv
# Clean without outlier removal
uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv --no-outliers -o clean.csv
# Save cleaning report as JSON
uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv -o clean.csv --report report.json
# Quality check only (no cleaning)
uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv --check-only
The clean.py script runs the full cleaning pipeline: deduplication, type fixing, missing value handling, outlier removal, and validation. It prints a report to stderr and outputs the cleaned CSV.
Cleaning order (always follow this sequence)
| Step |
Operation |
Strategy |
| 1 |
Remove duplicates |
drop_duplicates(subset=key_cols) |
| 2 |
Fix data types |
Auto-detect dates, cast numerics, strip strings |
| 3 |
Handle missing |
Numeric: median. Categorical: mode/"unknown". Critical: drop |
| 4 |
Remove outliers |
IQR (1.5x) or Z-score (threshold=3) |
| 5 |
Normalize text |
Lowercase, strip whitespace |
| 6 |
Encode categoricals |
Label (ordinal) or one-hot (nominal) |
| 7 |
Validate ranges |
Domain constraints (age>0, price>=0) |
| 8 |
Generate report |
Before/after stats |
Alternative frameworks
Polars (large datasets, faster)
import polars as pl
def prepare(df: pl.DataFrame) -> pl.DataFrame:
return (
df.unique()
.with_columns([
pl.col(c).fill_null(pl.col(c).median()) for c in df.select(pl.col(pl.Float64)).columns
])
.with_columns([
pl.col(c).fill_null("unknown") for c in df.select(pl.col(pl.Utf8)).columns
])
)
PySpark (distributed)
from pyspark.sql import DataFrame
from pyspark.sql.functions import col, mean
def prepare(df: DataFrame) -> DataFrame:
df = df.dropDuplicates()
for field in df.schema.fields:
if field.dataType.simpleString() in ("double", "float", "int"):
avg = df.select(mean(col(field.name))).first()[0]
df = df.fillna({field.name: avg or 0})
elif field.dataType.simpleString() == "string":
df = df.fillna({field.name: "unknown"})
return df
Pipeline config (YAML template)
cleaning_pipeline:
remove_duplicates:
enabled: true
subset: ['id', 'email']
keep: 'first'
missing_values:
strategy: auto
drop_threshold: 50 # Drop columns >50% missing
numeric_fill: median
categorical_fill: mode
outliers:
method: iqr
threshold: 1.5
columns: ['age', 'price']
validation:
ranges:
age: [0, 120]
price: [0, 1000000]
required_columns: ['id', 'name']
Phase 3: Feature Engineering
Transforms clean data into model-ready features. Run the script for automated engineering, or use the recipes below for custom transforms.
Decision guide
| Data type |
Transform |
| Skewed numeric |
Log, sqrt |
| High cardinality categorical |
Target/frequency encoding |
| Low cardinality categorical |
One-hot |
| Datetime |
Year/month/day + cyclical |
| Free text |
Length, word count |
| Multiple numeric |
Interactions, ratios |
| Time series |
Rolling stats, lags, diffs |
| Grouped data |
Aggregations, deviation from mean |
Quick start
# Auto-engineer all columns
uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv -o data/features.csv
# Engineer specific columns with target encoding
uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --cols age income category --target price -o features.csv
# Generate interaction features
uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --interactions -o features.csv
# Time series features
uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --cols revenue --types timeseries -o features.csv
# Group aggregations
uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --group segment revenue -o features.csv
# Summary as JSON
uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --json
Flags: --cols (specific columns), --types (numeric, categorical, datetime, text, timeseries), --target (target column for encoding), --interactions, --group GROUP_COL AGG_COL, --json, -o OUTPUT
Feature selection (after engineering)
from sklearn.feature_selection import mutual_info_classif
mi = mutual_info_classif(X.fillna(0), y, random_state=42)
top = pd.Series(mi, index=X.columns).sort_values(ascending=False).head(20)
print(top)
Rules
- Always keep original data — clean into a copy
- Log every step with counts
- Remove duplicates BEFORE handling nulls
- Test on a sample before full dataset
- EDA findings drive cleaning decisions — don't clean blind
- Feature engineering follows cleaning — engineer from clean data, never raw
1---2name: data-prep3description: Explore, clean, and engineer datasets end-to-end: statistical profiling, distribution checks, missing value analysis, duplicate detection, outlier removal, type fixing, encoding, create features, encode categories, transform columns, add rolling windows, build interaction terms, and feature engineering. Supports pandas, polars, and PySpark. Use when the user wants to explore data, profile columns, understand a dataset, clean data, handle missing values, remove duplicates, fix data types, preprocess a dataset before modeling, create features, encode categories, transform columns, add rolling windows, build interaction terms, or do feature engineering.4---56# Data Preparation: Explore & Clean78Two-phase workflow for systematic data preparation. Always run EDA first — findings drive the cleaning strategy.910## Available scripts1112| Script | Usage |13|--------|-------|14| [eda.py](scripts/eda.py) | `uv run ${CLAUDE_SKILL_DIR}/scripts/eda.py data.csv --target price` |15| [clean.py](scripts/clean.py) | `uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv -o clean.csv` |16| [engineer_features.py](scripts/engineer_features.py) | `uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv -o data/features.csv` |1718---1920## Phase 1: Exploratory Data Analysis2122### Quick start2324```bash25uv run ${CLAUDE_SKILL_DIR}/scripts/eda.py $ARGUMENTS26uv run ${CLAUDE_SKILL_DIR}/scripts/eda.py data/train.csv --target price27```2829The [eda.py](scripts/eda.py) script runs all 9 checks below in order and prints a complete report.3031### Column Classification3233Before profiling, classify each column:3435| Type | Description | Examples |36|------|-------------|---------|37| **Identifier** | Unique keys, foreign keys | user_id, order_id, session_id |38| **Dimension** | Categorical attributes for grouping | status, region, category, plan_type |39| **Metric** | Quantitative values for measurement | revenue, count, duration, score |40| **Temporal** | Dates and timestamps | created_at, event_date, updated_at |41| **Text** | Free-form text fields | description, notes, comment |42| **Boolean** | True/false flags | is_active, has_subscription |4344This classification drives which profiling checks to run on each column.4546### EDA Pipeline Steps47481. **Overview** — shape, memory, types, first rows492. **Missing values** — count and percentage per column, sorted by severity503. **Numeric features** — describe stats, zero-heavy warnings, negative value notes514. **Categorical features** — unique counts, top values, high cardinality, ID detection525. **Correlations** — pairs with |r| > 0.7 flagged for multicollinearity536. **Target analysis** — classification vs regression, imbalance check (with `--target`)547. **Duplicates** — exact duplicate row count and percentage558. **Completeness scoring** — GREEN (>99%) / YELLOW (95-99%) / ORANGE (80-95%) / RED (<80%)569. **Accuracy red flags** — placeholder values (0, -1, 999), round number bias, text placeholders5758### EDA report format5960```61=== EDA Report: {filename} ===6263Overview: 50,000 rows x 25 columns (15 numeric, 8 categorical, 2 datetime)6465Data quality:66 - Missing: col_a (12%), col_b (8%), col_c (6%)67 - Duplicates: 42 rows (0.08%)68 - High cardinality: user_id (unique per row)6970Key findings:71 1. feature_x and feature_y correlated (r=0.89) — multicollinearity72 2. Target imbalanced: 92% class 0, 8% class 173 3. price has 500 zeros — possibly coded missing values74 4. date_created spans 2019-2024 — check for temporal leakage7576Recommendations:77 1. Drop user_id (ID column, no predictive value)78 2. Handle class imbalance (SMOTE, class weights, stratified sampling)79 3. Investigate zero prices80 4. Use feature_x OR feature_y, not both81```8283### Red flags to always check8485- Columns that are 100% null86- Numeric columns stored as strings87- ID columns that could leak information88- Future data in features (temporal leakage)89- Constant columns (zero variance)90- Extreme class imbalance (>10:1)91- Placeholder values (0, -1, 999, 9999, "N/A", "TBD", "test")92- Default value dominance (one value has suspiciously high frequency)93- Round number bias (all values multiples of 5 or 10 — suggests estimation)94- Stale data (updated_at shows no recent changes in an active system)9596---9798## Phase 2: Data Cleaning99100### Quick start101102```bash103# Full cleaning pipeline104uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv -o clean.csv105106# Clean without outlier removal107uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv --no-outliers -o clean.csv108109# Save cleaning report as JSON110uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv -o clean.csv --report report.json111112# Quality check only (no cleaning)113uv run ${CLAUDE_SKILL_DIR}/scripts/clean.py data.csv --check-only114```115116The [clean.py](scripts/clean.py) script runs the full cleaning pipeline: deduplication, type fixing, missing value handling, outlier removal, and validation. It prints a report to stderr and outputs the cleaned CSV.117118### Cleaning order (always follow this sequence)119120| Step | Operation | Strategy |121|------|-----------|----------|122| 1 | Remove duplicates | `drop_duplicates(subset=key_cols)` |123| 2 | Fix data types | Auto-detect dates, cast numerics, strip strings |124| 3 | Handle missing | Numeric: median. Categorical: mode/"unknown". Critical: drop |125| 4 | Remove outliers | IQR (1.5x) or Z-score (threshold=3) |126| 5 | Normalize text | Lowercase, strip whitespace |127| 6 | Encode categoricals | Label (ordinal) or one-hot (nominal) |128| 7 | Validate ranges | Domain constraints (age>0, price>=0) |129| 8 | Generate report | Before/after stats |130131### Alternative frameworks132133#### Polars (large datasets, faster)134```python135import polars as pl136137def prepare(df: pl.DataFrame) -> pl.DataFrame:138 return (139 df.unique()140 .with_columns([141 pl.col(c).fill_null(pl.col(c).median()) for c in df.select(pl.col(pl.Float64)).columns142 ])143 .with_columns([144 pl.col(c).fill_null("unknown") for c in df.select(pl.col(pl.Utf8)).columns145 ])146 )147```148149#### PySpark (distributed)150```python151from pyspark.sql import DataFrame152from pyspark.sql.functions import col, mean153154def prepare(df: DataFrame) -> DataFrame:155 df = df.dropDuplicates()156 for field in df.schema.fields:157 if field.dataType.simpleString() in ("double", "float", "int"):158 avg = df.select(mean(col(field.name))).first()[0]159 df = df.fillna({field.name: avg or 0})160 elif field.dataType.simpleString() == "string":161 df = df.fillna({field.name: "unknown"})162 return df163```164165### Pipeline config (YAML template)166167```yaml168cleaning_pipeline:169 remove_duplicates:170 enabled: true171 subset: ['id', 'email']172 keep: 'first'173 missing_values:174 strategy: auto175 drop_threshold: 50 # Drop columns >50% missing176 numeric_fill: median177 categorical_fill: mode178 outliers:179 method: iqr180 threshold: 1.5181 columns: ['age', 'price']182 validation:183 ranges:184 age: [0, 120]185 price: [0, 1000000]186 required_columns: ['id', 'name']187```188189## Phase 3: Feature Engineering190191Transforms clean data into model-ready features. Run the script for automated engineering, or use the recipes below for custom transforms.192193### Decision guide194195| Data type | Transform |196|-----------|-----------|197| Skewed numeric | Log, sqrt |198| High cardinality categorical | Target/frequency encoding |199| Low cardinality categorical | One-hot |200| Datetime | Year/month/day + cyclical |201| Free text | Length, word count |202| Multiple numeric | Interactions, ratios |203| Time series | Rolling stats, lags, diffs |204| Grouped data | Aggregations, deviation from mean |205206### Quick start207208```bash209# Auto-engineer all columns210uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv -o data/features.csv211212# Engineer specific columns with target encoding213uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --cols age income category --target price -o features.csv214215# Generate interaction features216uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --interactions -o features.csv217218# Time series features219uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --cols revenue --types timeseries -o features.csv220221# Group aggregations222uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --group segment revenue -o features.csv223224# Summary as JSON225uv run ${CLAUDE_SKILL_DIR}/scripts/engineer_features.py data/clean.csv --json226```227228Flags: `--cols` (specific columns), `--types` (numeric, categorical, datetime, text, timeseries), `--target` (target column for encoding), `--interactions`, `--group GROUP_COL AGG_COL`, `--json`, `-o OUTPUT`229230### Feature selection (after engineering)231232```python233from sklearn.feature_selection import mutual_info_classif234235mi = mutual_info_classif(X.fillna(0), y, random_state=42)236top = pd.Series(mi, index=X.columns).sort_values(ascending=False).head(20)237print(top)238```239240---241242## Rules243244- Always keep original data — clean into a copy245- Log every step with counts246- Remove duplicates BEFORE handling nulls247- Test on a sample before full dataset248- EDA findings drive cleaning decisions — don't clean blind249- Feature engineering follows cleaning — engineer from clean data, never raw