New: Earn 20% as an AgentAlley affiliate. Share your link and earn on every sale you refer. Join now New: Earn 20% as an affiliate. Join now

Mapping mismatched column names to one schema

CSV/Excel Merger

by OneWave-AI · MIT license · ★ 306 on GitHub

CSV/Excel Merger cover

Merges, appends, joins, and deduplicates multiple CSV, TSV, and Excel files (including multi-sheet workbooks) with pandas - mapping mismatched column names to one schema, normalizing keys, resolving conflicting values, tracking which…

---
name: csv-excel-merger
description: Merges, appends, joins, and deduplicates multiple CSV, TSV, and Excel files (including multi-sheet workbooks) with pandas - mapping mismatched column names to one schema, normalizing keys, resolving conflicting values, tracking which file each row came from, and verifying the row math. Use when the user wants to combine spreadsheets or exports, stack monthly files, consolidate contact or lead lists, VLOOKUP-style join two sheets on an ID or email, or dedupe records across sources, even if they only say "put these together" or "clean up these lists into one".
---

# CSV/Excel Merger

Combine tabular files into one clean output without silently losing, duplicating, or corrupting rows.

## Workflow

Copy this checklist and track progress:

```
- [ ] 1. Profile inputs
- [ ] 2. Choose append vs join
- [ ] 3. Map columns and normalize keys
- [ ] 4. Merge and resolve conflicts

Overview

Combine tabular files into one clean output without silently losing, duplicating, or corrupting rows.

Manual install (npx, ZIP)
Claude Codenpx skills add OneWave-AI/claude-skills --skill csv-excel-merger -g -a claude-code
Claude Code (degit)npx degit OneWave-AI/claude-skills/csv-excel-merger#main ~/.claude/skills/csv-excel-merger
Claude.ai / desktopZip the folder and upload it under Settings → Capabilities → Skills.
Codex, Cursor, other agentsPoint them at SKILL.md. The other files are plain text.

Connects with Agent Alley MCP · agentalley.io/mcp

Files included

  • SKILL.md6.4 KB
Read SKILL.md here
OneWave-AI/main1 file shown
SKILL.md
Show the full text109 lines
csv-excel-merger/SKILL.md109 lines · 6.4 KB

CSV/Excel Merger

Combine tabular files into one clean output without silently losing, duplicating, or corrupting rows.

Workflow

Copy this checklist and track progress:

- [ ] 1. Profile inputs
- [ ] 2. Choose append vs join
- [ ] 3. Map columns and normalize keys
- [ ] 4. Merge and resolve conflicts
- [ ] 5. Verify row math
- [ ] 6. Write output and report
  1. Profile inputs. Run the bundled profiler first; it reports encoding, delimiter, rows, headers, candidate keys, and header overlap without changing anything:

    python scripts/profile_inputs.py file1.csv file2.xlsx
    

    Excel files are profiled per sheet. Confirm with the user which sheets count if a workbook has more than one.

  2. Choose the operation. This is the decision that most often goes wrong:

    • Append (stack) - same kind of records from different sources or periods (Jan + Feb exports, three lead lists). Use pd.concat, then dedupe.
    • Join (enrich) - different facts about the same entities (contacts + their deal values). Use pd.merge on a key.
    • Unsure? If the files share most columns, append. If they share only an ID column, join.
  3. Map columns and normalize keys. Build an explicit {original: unified} rename map per file (see references/merge_strategies.md for common variants) and show it to the user when any match is fuzzy. Normalize key columns before dedupe or join: strip whitespace, lowercase emails, strip non-digits from phones, unify date formats. Without this, [email protected] and [email protected] survive as two people.

  4. Merge. Read every file with dtype=str so IDs, ZIP codes, and phone numbers keep leading zeros, then convert specific columns afterward.

    import pandas as pd
    
    frames = []
    for path, rename in [("jan.csv", {"E-mail": "email"}), ("feb.xlsx", {"Email Address": "email"})]:
        df = (pd.read_excel(path, dtype=str) if path.endswith((".xlsx", ".xls"))
              else pd.read_csv(path, dtype=str, encoding="utf-8-sig",  # use the profiler's encoding
                            keep_default_na=False))
        df = df.rename(columns=rename)
        df["email"] = df["email"].str.strip().str.lower()
        df["source_file"] = path          # lineage for every row
        frames.append(df)
    
    combined = pd.concat(frames, ignore_index=True, sort=False)
    # Blank keys are not duplicates of each other: set them aside before deduping.
    has_key = combined["email"].fillna("") != ""
    # Later files win: list the most recent source last, then keep="last".
    deduped = combined[has_key].drop_duplicates(subset=["email"], keep="last")
    no_key = combined[~has_key]
    merged = pd.concat([deduped, no_key], ignore_index=True)
    

    For a join, make pandas enforce the relationship you expect so a duplicate key raises instead of multiplying rows:

    out = pd.merge(contacts, deals, on="email", how="left",
                   validate="one_to_one", indicator=True)
    unmatched = out[out["_merge"] == "left_only"]
    

    Conflict strategies (keep first/last/most complete, combine fields, flag for review) are in references/merge_strategies.md.

  5. Verify before reporting. Never hand back a merge without checking it:

    rows_in = sum(len(f) for f in frames)
    assert len(merged) > 0, "merge produced an empty frame"
    assert len(merged) <= rows_in, "more rows out than in: check the join keys"
    assert deduped["email"].is_unique, "duplicate keys remain after dedupe"
    print(f"in={rows_in} out={len(merged)} removed={rows_in - len(merged)} blank_keys={len(no_key)}")
    print(merged["source_file"].value_counts())
    

    Spot-check three removed duplicates by hand against the source files; the asserts prove the math, not that the right row won.

  6. Write output and report. Use the layout in references/output_template.md.

    • CSV for Excel users: to_csv(path, index=False, encoding="utf-8-sig") (the BOM makes Excel read accents correctly).
    • Excel: to_excel(path, index=False) with openpyxl installed. A sheet holds at most 1,048,576 rows; split or use CSV/Parquet beyond that.
    • Also write conflicts_review.csv or unmatched.csv when those sets are non-empty.

pandas version notes

Current pandas is 3.x (Python 3.11+). Differences that affect merges:

  • Text columns default to the str dtype, not object. Check pd.api.types.is_string_dtype(col) instead of dtype == object.
  • Copy-on-Write is always on. Chained assignment such as df[col][mask] = x never updates df (pandas only warns); use df.loc[mask, col] = x.
  • Parsed datetimes default to microsecond resolution. Call .dt.as_unit("ns") before casting to integers if something downstream expects nanoseconds.
  • pd.read_excel(..., engine="calamine") (needs python-calamine) reads large workbooks much faster than openpyxl.

The code in this skill also runs on pandas 2.2.

Failure modes to check for

  • Row explosion on join - duplicate keys on both sides multiply rows. validate= catches it.
  • Leading zeros lost - reading without dtype=str turns 01234 into 1234.
  • Excel-mangled values - long IDs already shown as 1.23E+15 or dates already reformatted in the source file cannot be recovered by pandas; flag them.
  • Header rows not on line 1 - exports with a title block need skiprows= or header=.
  • Mixed encodings - one file in cp1252 among UTF-8 files shows up as é artifacts. The profiler reports the encoding per file.
  • Silent column drops - a column present in only one file becomes mostly empty after append. Keep it and report its completeness; never drop data without saying so.
  • Large files (over a few hundred MB) - read with chunksize= or use Polars/DuckDB, and dedupe with a key set instead of loading everything into memory.
1---
2name: csv-excel-merger
3description: Merges, appends, joins, and deduplicates multiple CSV, TSV, and Excel files (including multi-sheet workbooks) with pandas - mapping mismatched column names to one schema, normalizing keys, resolving conflicting values, tracking which file each row came from, and verifying the row math. Use when the user wants to combine spreadsheets or exports, stack monthly files, consolidate contact or lead lists, VLOOKUP-style join two sheets on an ID or email, or dedupe records across sources, even if they only say "put these together" or "clean up these lists into one".
4---
5 
6# CSV/Excel Merger
7 
8Combine tabular files into one clean output without silently losing, duplicating, or corrupting rows.
9 
10## Workflow
11 
12Copy this checklist and track progress:
13 
14```
15- [ ] 1. Profile inputs
16- [ ] 2. Choose append vs join
17- [ ] 3. Map columns and normalize keys
18- [ ] 4. Merge and resolve conflicts
19- [ ] 5. Verify row math
20- [ ] 6. Write output and report
21```
22 
231. **Profile inputs.** Run the bundled profiler first; it reports encoding, delimiter, rows, headers, candidate keys, and header overlap without changing anything:
24 
25 ```bash
26 python scripts/profile_inputs.py file1.csv file2.xlsx
27 ```
28 
29 Excel files are profiled per sheet. Confirm with the user which sheets count if a workbook has more than one.
30 
312. **Choose the operation.** This is the decision that most often goes wrong:
32 - **Append (stack)** - same kind of records from different sources or periods (Jan + Feb exports, three lead lists). Use `pd.concat`, then dedupe.
33 - **Join (enrich)** - different facts about the same entities (contacts + their deal values). Use `pd.merge` on a key.
34 - Unsure? If the files share most columns, append. If they share only an ID column, join.
35 
363. **Map columns and normalize keys.** Build an explicit `{original: unified}` rename map per file (see [references/merge_strategies.md](references/merge_strategies.md) for common variants) and show it to the user when any match is fuzzy. Normalize key columns before dedupe or join: strip whitespace, lowercase emails, strip non-digits from phones, unify date formats. Without this, `[email protected] ` and `[email protected]` survive as two people.
37 
384. **Merge.** Read every file with `dtype=str` so IDs, ZIP codes, and phone numbers keep leading zeros, then convert specific columns afterward.
39 
40 ```python
41 import pandas as pd
42 
43 frames = []
44 for path, rename in [("jan.csv", {"E-mail": "email"}), ("feb.xlsx", {"Email Address": "email"})]:
45 df = (pd.read_excel(path, dtype=str) if path.endswith((".xlsx", ".xls"))
46 else pd.read_csv(path, dtype=str, encoding="utf-8-sig", # use the profiler's encoding
47 keep_default_na=False))
48 df = df.rename(columns=rename)
49 df["email"] = df["email"].str.strip().str.lower()
50 df["source_file"] = path # lineage for every row
51 frames.append(df)
52 
53 combined = pd.concat(frames, ignore_index=True, sort=False)
54 # Blank keys are not duplicates of each other: set them aside before deduping.
55 has_key = combined["email"].fillna("") != ""
56 # Later files win: list the most recent source last, then keep="last".
57 deduped = combined[has_key].drop_duplicates(subset=["email"], keep="last")
58 no_key = combined[~has_key]
59 merged = pd.concat([deduped, no_key], ignore_index=True)
60 ```
61 
62 For a join, make pandas enforce the relationship you expect so a duplicate key raises instead of multiplying rows:
63 
64 ```python
65 out = pd.merge(contacts, deals, on="email", how="left",
66 validate="one_to_one", indicator=True)
67 unmatched = out[out["_merge"] == "left_only"]
68 ```
69 
70 Conflict strategies (keep first/last/most complete, combine fields, flag for review) are in [references/merge_strategies.md](references/merge_strategies.md).
71 
725. **Verify before reporting.** Never hand back a merge without checking it:
73 
74 ```python
75 rows_in = sum(len(f) for f in frames)
76 assert len(merged) > 0, "merge produced an empty frame"
77 assert len(merged) <= rows_in, "more rows out than in: check the join keys"
78 assert deduped["email"].is_unique, "duplicate keys remain after dedupe"
79 print(f"in={rows_in} out={len(merged)} removed={rows_in - len(merged)} blank_keys={len(no_key)}")
80 print(merged["source_file"].value_counts())
81 ```
82 
83 Spot-check three removed duplicates by hand against the source files; the asserts prove the math, not that the right row won.
84 
856. **Write output and report.** Use the layout in [references/output_template.md](references/output_template.md).
86 - CSV for Excel users: `to_csv(path, index=False, encoding="utf-8-sig")` (the BOM makes Excel read accents correctly).
87 - Excel: `to_excel(path, index=False)` with openpyxl installed. A sheet holds at most 1,048,576 rows; split or use CSV/Parquet beyond that.
88 - Also write `conflicts_review.csv` or `unmatched.csv` when those sets are non-empty.
89 
90## pandas version notes
91 
92Current pandas is 3.x (Python 3.11+). Differences that affect merges:
93- Text columns default to the `str` dtype, not `object`. Check `pd.api.types.is_string_dtype(col)` instead of `dtype == object`.
94- Copy-on-Write is always on. Chained assignment such as `df[col][mask] = x` never updates `df` (pandas only warns); use `df.loc[mask, col] = x`.
95- Parsed datetimes default to microsecond resolution. Call `.dt.as_unit("ns")` before casting to integers if something downstream expects nanoseconds.
96- `pd.read_excel(..., engine="calamine")` (needs `python-calamine`) reads large workbooks much faster than openpyxl.
97 
98The code in this skill also runs on pandas 2.2.
99 
100## Failure modes to check for
101 
102- **Row explosion on join** - duplicate keys on both sides multiply rows. `validate=` catches it.
103- **Leading zeros lost** - reading without `dtype=str` turns `01234` into `1234`.
104- **Excel-mangled values** - long IDs already shown as `1.23E+15` or dates already reformatted in the source file cannot be recovered by pandas; flag them.
105- **Header rows not on line 1** - exports with a title block need `skiprows=` or `header=`.
106- **Mixed encodings** - one file in cp1252 among UTF-8 files shows up as `é` artifacts. The profiler reports the encoding per file.
107- **Silent column drops** - a column present in only one file becomes mostly empty after append. Keep it and report its completeness; never drop data without saying so.
108- **Large files** (over a few hundred MB) - read with `chunksize=` or use Polars/DuckDB, and dedupe with a key set instead of loading everything into memory.
109 
View all files on GitHub →

Details

License
MIT
Stars on the repo
★ 306
Checked
Source
OneWave-AI/claude-skills ↗
Creator
OneWave-AI · 16 listings
View creator

Similar skills

See all →
SkillReverse engineer native, managed, Electron/JavaScriptREAmorluto/rea · by morlutoReverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.★ 316k GitHub starsSkillBrainstorms ideas into designs before any creative workBrainstorming Ideas Into Designsobra/superpowers · by obraYou MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.★ 290k GitHub starsSkillRequires technical rigor and verificationCode Review Receptionobra/superpowers · by obraUse when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation★ 290k GitHub starsSkillDiagnosis loop for hard bugs and performance regressionsDiagnosing bugsmattpocock/skills · by mattpocockDiagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.★ 267k GitHub starsSkillImplement the result of /to-spec and /to-tickets in codeImplement specmattpocock/skills · by mattpocockImplement the result of /to-spec and /to-tickets in code.★ 267k GitHub starsSkillWriting a PR bodymattpocock/skills · by mattpocockUse when writing a PR body.★ 267k GitHub stars

More from OneWave-AI

SkillFinding security vulnerabilities , correctness bugsCode review proOneWave-AI/claude-skills · by OneWave-AIPerforms a deep code review of files, modules, a diff, or a branch - finding security vulnerabilities (mapped to OWASP Top 10:2025), correctness bugs, performance problems, and maintainability issues - and returns severity-ranked findings with evidence and concrete fixes. Use when the user asks to review, audit, or sanity-check code, asks "is this safe", "what's wrong with this", "find bugs", or wants a security or performance pass before shipping. For posting line comments on a GitHub pull request, use git-pr-reviewer.★ 306 GitHub starsSkillDesigns relational and document database schemasDatabase schema designerOneWave-AI/claude-skills · by OneWave-AIDesigns relational and document database schemas from requirements or an existing app - tables and collections, keys, relationships, constraints, indexes driven by real query patterns, ERDs, and reversible migration scripts - with PostgreSQL (including Supabase) as the default and MySQL, SQLite, and MongoDB supported. Use when the user wants to model data for a new app or feature, review or fix an existing schema, choose keys or indexes, plan multi-tenancy, normalize or denormalize, or write CREATE TABLE statements or migrations, even if they just describe their app and ask "how should I store this". For moving data between database providers, use database-migrator.★ 306 GitHub starsSkillBuild and run a labelled eval set for a System One model Evaluating a typed-decision configOneWave-AI/claude-skills · by OneWave-AIBuild and run a labelled eval set for a System One model (Jev, Von, or any typed-decision config), then sweep criteria wordings and thresholds against it. Use when a Jev/Von classification is wrong or unreliable, when choosing between the hosted API and a local open model, when tuning noul thresholds, or before shipping any typed-decision feature. Produces an accuracy-by-wording matrix and a calibrated threshold.★ 306 GitHub starsSkillExtract and analyze data from invoices, receiptsFinancial Document ParserOneWave-AI/claude-skills · by OneWave-AIExtract and analyze data from invoices, receipts, bank statements, and financial documents. Categorize expenses, track recurring charges, and generate expense reports. Use when user provides financial PDFs or images.★ 306 GitHub starsSkillWrites conversion-focused landing page copyLanding page copywriterOneWave-AI/claude-skills · by OneWave-AIWrites conversion-focused landing page copy - headlines, subheads, value propositions, feature and benefit blocks, social proof, FAQs, and CTA button text - using PAS, AIDA, or StoryBrand. Use when the user wants words for a landing page, sales page, product or pricing page, waitlist page, homepage hero, or ad-to-page flow, including rewriting weak copy, generating headline or CTA variants for A/B tests, or turning a product description into page sections. Use landing-page-optimizer instead for auditing an existing page's performance, and a frontend skill for building the page itself.★ 306 GitHub starsSkillAnalyze meeting transcripts to extract decisionsMeeting Intelligence SystemOneWave-AI/claude-skills · by OneWave-AIAnalyze meeting transcripts to extract decisions, action items, blockers, sentiment, and generate follow-up emails. Use when user provides meeting notes, transcripts, or recordings and needs structured summaries or action tracking.★ 306 GitHub stars