Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Cleans messy tabular datasets in pandas end-to-end — fixing dtypes, parsing dates and numbers, standardizing text, handling missing values, removing duplicates, detecting and treating outliers, and reshaping wide/long into tidy data. Use this skill when the user asks to "clean this CSV/Excel/dataframe", "fix data types", "handle missing values / NaNs", "remove duplicates", "deal with outliers", "standardize column names or categories", "parse dates", "melt/pivot/reshape", or to build a reproduci
.claude/skills/jayrha-pandas-data-cleaning/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-16 | ✗→✓ | ▲ Improved | 73% | 0% |
| case-23 | ✓→✗ | ▼ Worse | 55% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 63% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 84% | 0% |
| case-14 | ✓→✓ | = Same ✓ | 111% | 0% |
Keywords: pandas, data cleaning, dtypes, missing values, NaN, imputation, duplicates, outliers, IQR, z-score, tidy data, melt, pivot, normalize, standardize, parse dates, categorical, data quality, ETL preprocessing.
This skill turns a messy DataFrame into a tidy, correctly-typed, analysis-ready dataset using a repeatable, auditable workflow. The core principle: profile first, decide explicitly, transform with logging, validate after. Never mutate data silently — every fill, drop, or cast should be a deliberate, documented choice you can defend.
Treat cleaning as a pipeline that produces (1) the cleaned DataFrame and (2) a record of decisions. Prefer chained, non-mutating transforms (df.assign(...), .pipe(...)) over scattered in-place edits so the pipeline is reproducible top-to-bottom.
scripts/profile_data.py <path> (or replicate inline) to get shape, dtypes, per-column null counts/percentages, unique counts, sample values, and candidate problems (mixed types, high-cardinality strings, numeric-looking objects, constant columns). See references/cleaning-checklist.md."1,234", "$5.00", "12%") to numbers, parse dates with explicit formats, cast low-cardinality strings to category, and use nullable dtypes (Int64, boolean, string) where missing values must coexist with non-float types. See references/dtype-conversion.md."NA", "-", "unknown", -999, empty string) from real values and convert them to NaN/pd.NA. Then choose a strategy per column — drop, constant fill, statistical impute (mean/median/mode), forward/backfill for time series, or model-based — and document why. Use the decision framework below.subset=[...]). Decide keep policy (first/last/aggregate). Watch for near-duplicates from inconsistent text (handle those in step 4 first).references/outliers-and-reshaping.md.melt to go wide→long, pivot/pivot_table for long→wide, and str.split/explode to split packed columns. Confirm the result satisfies the three tidy rules.references/cleaning-checklist.md.templates/cleaning_report.md to summarize what was done and why.| Situation | Recommended strategy | |---|---| | Column >50–60% missing, not critical | Drop the column | | A few rows missing in a required key/target | Drop those rows | | Numeric, missing-at-random, skewed | Impute median | | Numeric, roughly symmetric | Impute mean (or median for robustness) | | Categorical | Impute mode, or add explicit "Missing" category | | Time series / ordered | ffill/bfill, or interpolate (.interpolate()) | | Missingness is itself informative | Keep NaN + add boolean was_missing flag | | Need non-float ints with NaN | Cast to nullable Int64, don't fill |
Rule of thumb: imputing changes the distribution. For modeling, prefer adding a missingness indicator alongside the imputed value so the model can learn from "was missing."
Q1 - 1.5*IQR, Q3 + 1.5*IQR) or |z| > 3.Given a column price of strings like "$1,299.00", "N/A", "":
pythondf["price"] = ( df["price"] .replace({"N/A": pd.NA, "": pd.NA}) .str.replace(r"[$,]", "", regex=True) .pipe(pd.to_numeric, errors="coerce") # bad parses -> NaN ) df["price"] = df["price"].fillna(df["price"].median()) # documented: skewed
See examples/clean_messy_sales.md for a full raw→clean walkthrough with a 12-column messy sales file, and run scripts/clean_pipeline.py --help for a configurable end-to-end cleaner.
.assign/.pipe so the whole recipe is one readable, rerunnable block. Avoid sprinkling inplace=True.errors="coerce", then inspect the new NaNs — they reveal unparseable values you'd otherwise miss.pd.to_datetime(s, format="%Y-%m-%d")) to avoid silent misparsing of ambiguous 01/02/03.Int64, boolean, string) instead of forcing floats just to hold NaN."unknown", -999, "-", whitespace) left as real data, poisoning means and joins. Always normalize these to NaN first.inplace=True chaining bugs and accidental SettingWithCopyWarning from chained indexing — use .loc and reassignment.float64 columns full of 1.0/2.0 because NaN forced float — cast to Int64 after cleaning.dayfirst vs default) silently swapping day/month."Acme " and "acme" survive as distinct.pivot when index/column pairs aren't unique; use pivot_table with an explicit aggfunc.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 26,798 | 23,861 | -11% | 1 | 1 | 0% | 6,210 | 7,644 | +23% | 0 | 0 | — |
case-02 | fail→fail | 26,265 | 24,493 | -7% | 1 | 1 | 0% | 5,641 | 7,372 | +31% | 0 | 0 | — |
case-03 | fail→fail | 7,676 | 6,595 | -14% | 1 | 1 | 0% | 1,679 | 3,170 | +89% | 0 | 0 | — |
case-04 | pass→pass | 10,455 | 6,485 | -38% | 1 | 1 | 0% | 1,945 | 3,164 | +63% | 0 | 0 | — |
case-05 | pass→pass | 7,825 | 5,164 | -34% | 1 | 1 | 0% | 1,625 | 2,984 | +84% | 0 | 0 | — |
case-14 | pass→pass | 8,634 | 7,486 | -13% | 1 | 1 | 0% | 1,639 | 3,453 | +111% | 0 | 0 | — |
case-06 | pass→pass | 9,794 | 4,929 | -50% | 1 | 1 | 0% | 1,911 | 3,017 | +58% | 0 | 0 | — |
case-07 | pass→pass | 9,055 | 7,164 | -21% | 1 | 1 | 0% | 1,835 | 3,040 | +66% | 0 | 0 | — |
case-08 | pass→pass | 9,543 | 9,270 | -3% | 1 | 1 | 0% | 2,009 | 3,548 | +77% | 0 | 0 | — |
case-09 | pass→pass | 9,105 | 23,997 | +164% | 1 | 1 | 0% | 2,058 | 3,524 | +71% | 0 | 0 | — |
case-10 | pass→pass | 8,236 | 3,978 | -52% | 1 | 1 | 0% | 1,475 | 2,570 | +74% | 0 | 0 | — |
case-11 | pass→pass | 13,821 | 9,247 | -33% | 1 | 1 | 0% | 2,449 | 3,695 | +51% | 0 | 0 | — |
case-12 | pass→pass | 3,838 | 21,101 | +450% | 1 | 1 | 0% | 725 | 2,615 | +261% | 0 | 0 | — |
case-13 | pass→pass | 11,227 | 6,653 | -41% | 1 | 1 | 0% | 2,119 | 3,106 | +47% | 0 | 0 | — |
case-15 | pass→pass | 12,670 | 17,725 | +40% | 1 | 1 | 0% | 2,181 | 3,400 | +56% | 0 | 0 | — |
case-16 | fail→pass | 14,161 | 12,940 | -9% | 1 | 1 | 0% | 2,671 | 4,613 | +73% | 0 | 0 | — |
case-17 | pass→pass | 8,385 | 21,502 | +156% | 1 | 1 | 0% | 1,520 | 3,275 | +115% | 0 | 0 | — |
case-18 | pass→pass | 8,499 | 5,380 | -37% | 1 | 1 | 0% | 1,420 | 2,780 | +96% | 0 | 0 | — |
case-19 | pass→pass | 8,387 | 3,218 | -62% | 1 | 1 | 0% | 1,465 | 2,454 | +68% | 0 | 0 | — |
case-20 | pass→pass | 11,330 | 8,727 | -23% | 1 | 1 | 0% | 2,270 | 3,509 | +55% | 0 | 0 | — |
case-21 | pass→pass | 11,183 | 10,602 | -5% | 1 | 1 | 0% | 2,227 | 4,192 | +88% | 0 | 0 | — |
case-22 | pass→pass | 16,175 | 9,211 | -43% | 1 | 1 | 0% | 3,072 | 3,714 | +21% | 0 | 0 | — |
case-23 | pass→fail | 16,017 | 15,749 | -2% | 1 | 1 | 0% | 3,486 | 5,396 | +55% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/3/2026 | — |
Other measured skills in the registry, with their headline benchmark lift.