Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Merge multiple CSV/Excel files with intelligent column matching, data deduplication, and conflict resolution. Handles different schemas, formats, and combines data sources. Use when users need to merge spreadsheets, combine data exports, or consolidate multiple files into one.
.claude/skills/onewave-ai-csv-excel-merger/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 27% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-15 | ✗→✓ | ▲ Improved | -13% | 0% |
Merge multiple CSV or Excel files with automatic column matching, deduplication, and conflict resolution.
references/merge_strategies.md — column matching, conflict resolution, and dedup optionsreferences/output_template.md — the merge-report formatreferences/merge_strategies.md for the matching heuristics and the full set of options.python import pandas as pd
df1 = pd.read_csv("file1.csv") df2 = pd.read_csv("file2.csv")
# Normalize, then map column names onto the unified schema for df in (df1, df2): df.columns = df.columns.str.lower().str.strip() df2 = df2.rename(columns={"firstname": "first_name", "e_mail": "email"})
merged = pd.concat(df1, df2], ignore_index=True) merged = merged.drop_duplicates(subset="email"], keep="last") merged.to_csv("merged_output.csv", index=False)
references/output_template.md, then offer export options: CSV (UTF-8), Excel (.xlsx), JSON, SQL INSERT statements, or Parquet for large datasets.Never hand back a merge without checking it. After merging, assert the row math holds and the key is actually unique:
pythontotal_in = len(df1) + len(df2) assert len(merged) > 0, "merge produced an empty frame" assert len(merged) <= total_in, "more rows than inputs — check the concat/join" assert merged["email"].is_unique, "duplicate keys remain after dedup" print(f"in: {total_in} rows | out: {len(merged)} rows | removed: {total_in - len(merged)}") print(f"null keys: {merged['email'].isna().sum()} | columns: {list(merged.columns)}")
Report rows in vs. out, duplicates removed, and per-column completeness so the user can sanity-check the numbers against their own expectations.
subset=["email", "company"].pd.read_csv(path, chunksize=...)), report progress, and estimate memory before loading everything at once.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 7,585 | 18,892 | +149% | 1 | 1 | 0% | 536 | 1,457 | +172% | 0 | 0 | — |
case-02 | fail→pass | 16,534 | 24,770 | +50% | 1 | 1 | 0% | 3,163 | 4,931 | +56% | 0 | 0 | — |
case-03 | fail→fail | 9,354 | 3,696 | -60% | 1 | 1 | 0% | 1,847 | 1,485 | -20% | 0 | 0 | — |
case-04 | fail→pass | 14,791 | 13,401 | -9% | 1 | 1 | 0% | 2,795 | 3,556 | +27% | 0 | 0 | — |
case-05 | pass→pass | 11,542 | 9,277 | -20% | 1 | 1 | 0% | 1,928 | 2,653 | +38% | 0 | 0 | — |
case-06 | pass→pass | 10,162 | 7,938 | -22% | 1 | 1 | 0% | 1,882 | 2,289 | +22% | 0 | 0 | — |
case-07 | pass→pass | 11,393 | 8,569 | -25% | 1 | 1 | 0% | 2,104 | 2,553 | +21% | 0 | 0 | — |
case-08 | fail→pass | 16,941 | 17,464 | +3% | 1 | 1 | 0% | 2,714 | 4,175 | +54% | 0 | 0 | — |
case-09 | fail→pass | 10,259 | 8,192 | -20% | 1 | 1 | 0% | 1,729 | 2,289 | +32% | 0 | 0 | — |
case-10 | pass→pass | 13,915 | 12,051 | -13% | 1 | 1 | 0% | 2,507 | 3,189 | +27% | 0 | 0 | — |
case-11 | pass→pass | 6,142 | 6,081 | -1% | 1 | 1 | 0% | 1,058 | 2,100 | +98% | 0 | 0 | — |
case-12 | pass→pass | 14,990 | 13,189 | -12% | 1 | 1 | 0% | 2,541 | 2,820 | +11% | 0 | 0 | — |
case-13 | pass→pass | 12,293 | 6,248 | -49% | 1 | 1 | 0% | 2,088 | 2,087 | -0% | 0 | 0 | — |
case-14 | pass→pass | 15,596 | 12,155 | -22% | 1 | 1 | 0% | 2,236 | 2,939 | +31% | 0 | 0 | — |
case-15 | fail→pass | 10,867 | 3,063 | -72% | 1 | 1 | 0% | 1,742 | 1,513 | -13% | 0 | 0 | — |
case-16 | pass→pass | 8,495 | 9,373 | +10% | 1 | 1 | 0% | 1,588 | 2,759 | +74% | 0 | 0 | — |
case-17 | pass→pass | 5,850 | 5,121 | -12% | 1 | 1 | 0% | 1,051 | 1,930 | +84% | 0 | 0 | — |
case-18 | pass→pass | 16,003 | 12,516 | -22% | 1 | 1 | 0% | 2,765 | 3,087 | +12% | 0 | 0 | — |
case-19 | pass→pass | 11,771 | 8,198 | -30% | 1 | 1 | 0% | 2,055 | 2,476 | +20% | 0 | 0 | — |
case-20 | pass→pass | 15,132 | 9,873 | -35% | 1 | 1 | 0% | 2,489 | 2,645 | +6% | 0 | 0 | — |
case-21 | pass→pass | 9,024 | 6,269 | -31% | 1 | 1 | 0% | 1,786 | 2,140 | +20% | 0 | 0 | — |
case-22 | pass→pass | 4,033 | 3,969 | -2% | 1 | 1 | 0% | 839 | 1,744 | +108% | 0 | 0 | — |
case-23 | pass→pass | 3,337 | 3,280 | -2% | 1 | 1 | 0% | 654 | 1,519 | +132% | 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, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +22 percentage points is the difference between those two pass rates over the 22 comparable cases.
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.
Other measured skills in the registry, with their headline benchmark lift.