Skip to main content
skill CX Codex

csv-excel-merger — Claude Code skill: merge CSV and Excel files

A skill that folds several CSV, TSV and Excel files into one: stacks, joins on a key, drops duplicates and checks the row math.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add OneWave-AI/claude-skills --skill csv-excel-merger -a claude-code
Open on GitHub

What csv-excel-merger does

The skill runs on pandas and handles the part where rows usually go missing: it maps mismatched column names onto one schema, normalizes keys (whitespace, email case, date formats), resolves conflicting values and tags every row with its source file. A bundled scripts/profile_inputs.py runs first and reports encoding, delimiter, row counts, candidate keys and header overlap without touching anything. Then you pick the operation: append for the same kind of records across periods, join for different facts about the same entities on an ID or email.

Files are read with dtype=str, so leading zeros in IDs, ZIP codes and phone numbers survive. For joins the skill passes validate="one_to_one", which makes pandas raise on a duplicate key instead of multiplying rows. Before handing anything back it checks the arithmetic: rows in, rows out, rows removed, blank keys set aside. Blank keys are not treated as duplicates of each other.

You need Python and pandas; Excel reading wants openpyxl, and engine="calamine" with python-calamine is much faster on large workbooks. One caveat worth knowing up front: values Excel already mangled in the source file, like a long ID shown as 1.23E+15 or a reformatted date, cannot be recovered by pandas — the skill only flags them. A single Excel sheet holds at most 1,048,576 rows, so beyond that you split the output or go with CSV. For files over a few hundred MB, read with chunksize= or reach for Polars and DuckDB.

What it helps with

  • stack January and February exports into one file
  • join contacts to their deal values on email
  • dedupe records coming from three sources
  • map mismatched column names onto one schema
  • catch a join that multiplied rows

Example prompts for the agent

  • Combine jan.csv and feb.xlsx into one file, E-mail and Email Address are both email
  • Do a VLOOKUP-style join of contacts.csv and deals.xlsx on email and show who has no deal
  • Dedupe these three lead lists and add a column with the source file name

How to install csv-excel-merger

1Skills CLI (Vercel)

npx skills add OneWave-AI/claude-skills --skill csv-excel-merger -a claude-code

2Manually: put the files in place

~/.claude/skills/csv-excel-merger/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/csv-excel-merger/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about csv-excel-merger

How do I install csv-excel-merger in Claude?

Run npx skills add OneWave-AI/claude-skills --skill csv-excel-merger -a claude-code. The skill lands in ~/.claude/skills/csv-excel-merger/SKILL.md.

What does csv-excel-merger do?

It combines CSV, TSV and Excel files into one output: appends, joins on a key, deduplicates and verifies that the row count did not grow or shrink unexpectedly.

Which pandas version does it need?

The code targets pandas 3.x on Python 3.11+ and also runs on pandas 2.2. In 3.x text columns default to the str dtype and Copy-on-Write is always on, so chained assignment like df[col][mask] = x silently does nothing — use df.loc[mask, col] = x.

Source text

SKILL.md · MIT
---
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
- [ ] 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:

   ```bash
   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](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.

   ```python
   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:

   ```python
   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](references/merge_strategies.md).

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

   ```python
   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](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.

© OneWave-AI · MIT · OneWave-AI/claude-skills

Similar skills

All skills for Data and databases →

From the same set58

landing-page-copywriter Writes conversion copy for landing pages, waitlists, and pricing tables using PAS, AIDA, or StoryBrand. Generates structured sections and headline variants without touching frontend code. CX Codex +1 6,500installs code-review-pro Reviews files, a module, a diff or a branch and returns severity-ranked findings: vulnerabilities, correctness bugs, performance and maintainability issues, each with evidence and a fix. CX Codex +1 2,600installs screenshot-to-code Turns a screenshot, mockup or Figma export into working front-end code, then renders it and compares the result against the image. Handy when the design exists but the markup does not. CX Codex +1 1,300installs technical-writer Writes and restructures READMEs, tutorials, how-to guides, runbooks and release notes grounded in the actual code. Useful when new developers need to understand a project. CX Codex +1 1,000installs sports-betting-analyzer Analyzes spreads, over/unders, and prop bets using historical trends and situational stats. Built for educational and entertainment betting analysis. CX Codex +1 808installs css-animation-creator A coding-agent skill for writing CSS animations, transitions, micro-interactions and scroll effects. Useful when you need motion in a UI without hand-tuning every duration. CX Codex +1 805installs job-application-optimizer This skill rewrites your resume for a specific posting, drafts a cover letter and builds interview questions. Useful when an application has to go out today. CX Codex +1 698installs knowledge-base-builder Turns support tickets into knowledge base articles: FAQ sections, step-by-step tutorials with screenshot placement, video scripts and deflection metrics. CX Codex +1 667installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor