CSV/Excel Merger
Combine tabular files into one clean output without silently losing, duplicating, or corrupting rows.
Workflow
Copy this checklist and track progress:
-
Profile inputs. Run the bundled profiler first; it reports encoding, delimiter, rows, headers, candidate keys, and header overlap without changing anything:
Excel files are profiled per sheet. Confirm with the user which sheets count if a workbook has more than one.
-
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.mergeon a key. - Unsure? If the files share most columns, append. If they share only an ID column, join.
- Append (stack) - same kind of records from different sources or periods (Jan + Feb exports, three lead lists). Use
-
Map columns and normalize keys. Build an explicit
{original: unified}rename map per file (see references/merge_strategies.md [blocked] 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. -
Merge. Read every file with
dtype=strso IDs, ZIP codes, and phone numbers keep leading zeros, then convert specific columns afterward.For a join, make pandas enforce the relationship you expect so a duplicate key raises instead of multiplying rows:
Conflict strategies (keep first/last/most complete, combine fields, flag for review) are in references/merge_strategies.md [blocked].
-
Verify before reporting. Never hand back a merge without checking it:
Spot-check three removed duplicates by hand against the source files; the asserts prove the math, not that the right row won.
-
Write output and report. Use the layout in references/output_template.md [blocked].
- 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.csvorunmatched.csvwhen those sets are non-empty.
- CSV for Excel users:
pandas version notes
Current pandas is 3.x (Python 3.11+). Differences that affect merges:
- Text columns default to the
strdtype, notobject. Checkpd.api.types.is_string_dtype(col)instead ofdtype == object. - Copy-on-Write is always on. Chained assignment such as
df[col][mask] = xnever updatesdf(pandas only warns); usedf.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")(needspython-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=strturns01234into1234. - Excel-mangled values - long IDs already shown as
1.23E+15or 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=orheader=. - 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.

