Compare a baseline CSV against a current one
Load two exports, name the number you care about and the column that splits it, and this page totals both files and subtracts. You get the headline movement, the categories ranked by how much they moved it, anything that appeared or disappeared, and a note of every column that changed. Both files stay in your browser and neither is uploaded.
Writing the result up for somebody else? Read the variance analysis walkthrough
How the two files are lined up
Nothing here matches rows to rows. Row order is ignored, row counts are allowed to differ, and there is no key column to nominate. Both files are aggregated first and the two aggregates are subtracted. That means the only things that have to agree between the files are the names of two columns: the one holding the number, and the one holding the categories.
- Header rows are resolved separately. Each file gets its own header. Blank or duplicated header cells are given usable names and the fix is reported in the warnings, so a file with two columns called
totaldoes not silently lose one. - Shared columns are worked out by name. A column counts as shared when the same header text appears in both files. Position does not matter. Move a column from third place to seventh and it is still the same column here.
- Category keys are trimmed. Leading and trailing spaces are stripped before values are bucketed, so
WestandWestland together. An empty value becomes a category named(blank)rather than being dropped, because a hole in the breakdown column is usually the finding. - Values that are not numbers are skipped and counted. A cell holding
N/Ain the metric column is left out of both totals, and the warnings say how many were skipped on each side. An empty cell is simply not added; only text that fails to parse is counted as unparsed. - Row counts come out separately. The headline block prints the baseline row count and the current row count as two numbers. They are never averaged or reconciled.
The ranking is by the size of the movement, ignoring its sign. A category that fell by 900 sorts above one that rose by 700, which is what you want when the question is what moved the total most. The report names the top fifteen; the driver CSV holds all of them.
Worked example: four regions and one arithmetic surprise
Two tiny files. First march.csv:
region,revenue,note
East,4000,
West,3000,ok
North,2000,ok
Then april.csv, which has one extra column and one extra region:
region,revenue,note,coupon
East,4400,,SPRING
West,2100,,
North,2000,ok,
Central,700,,SPRING
Set the metric to revenue, the aggregate to Sum, the breakdown to region, and the two labels to March and April. Baseline sums to 9,000 and current to 9,200, so the whole business moved by 200. Here is the report:
Variance report: March to April
Metric: sum of revenue Broken down by: region
HEADLINE
--------
March 9,000
April 9,200
change +200 (+2.2%)
rows 3 to 4
DRIVERS
-------
category baseline current change change % of net
West 3,000 2,100 -900 -30.0% -450.0%
Central 0 700 +700 n/a +350.0% NEW
East 4,000 4,400 +400 +10.0% +200.0%
North 2,000 2,000 +0 +0.0% +0.0%
NEW CATEGORIES (1)
--------------
Central 700 (1 rows)
SCHEMA DRIFT
------------
Added: coupon
COMPLETENESS DRIFT
------------------
column March blank April blank change
note 33.3% 75.0% +41.7 pp
Percentages use each file's own row count (3 and 4), not a shared one.
Check the last column by hand. The net change is 200. West moved by minus 900, and minus 900 divided by 200 is minus 4.5, which the tool prints as minus 450.0 per cent. Central contributed 700 over 200, or plus 350.0 per cent. East gives plus 200.0 per cent and North contributes nothing. Add them up: minus 450 plus 350 plus 200 plus 0 is exactly 100. The shares always sum to 100 of the net change, which is why they can each be enormous when the net change is small. That is a property of the arithmetic and not a bug.
Two other things in that output are worth pointing at. Central has no percentage in the change % column because its baseline was zero, and there is no percentage change from zero to anything. And the completeness line uses 3 as the denominator on the March side and 4 on the April side: one blank out of three rows is 33.3 per cent, three blanks out of four rows is 75.0 per cent. Sharing a single denominator across two files of different length would have produced a number that describes neither file.
The coupon column appears only in April, so it lands under schema drift and is not eligible as a metric or a breakdown. The completeness table only covers columns that both files have, since a column that did not exist has no earlier blank rate to compare against.
What each control does
- Metric. The numeric column being explained. It offers only columns both files have. Leave it on Count rows and the comparison becomes a row count, which is the right choice when the question is how many orders arrived rather than how much they were worth. If you do not pick one, the first numeric column shared by the two files is chosen for you.
- Aggregate. Sum, Average or Count. Sum for money, units and durations. Average for a price, a rate or a score, where adding the values up would be meaningless. Count ignores the metric entirely and totals rows. When neither file has a shared numeric column, Count is the default.
- Break down by. The column whose distinct values become the driver rows. Region, channel, plan, product line, campaign. Set it to Headline only and the report skips the driver table and gives you the two totals and the change. The auto-pick prefers a shared text column with more than one value and at most fifty distinct values, which is usually the right guess.
- Baseline label and Current label. Free text that replaces the words Baseline and Current everywhere in the output, including the column headings of the completeness table. Type March and April, or FY24 and FY25. This is cosmetic and it matters more than it sounds: a report whose columns say Baseline gets questions about which file that was.
- Download. Which of the four formats the download button hands you. The on-screen preview is always the text report regardless of this setting, so you can read first and choose the format after.
- Save this setup as. Type a name and this run's configuration is written to your browser. Leave it empty and nothing is written. Saving is a field rather than a button because the widget runs one action, and writing on every single run would be surprising.
- Load a saved setup. Type the name back and the stored metric, breakdown, aggregate and labels are used as the starting point. Anything you have explicitly chosen on the page still wins, and a saved column name is ignored if it is no longer in both files, so last quarter's configuration cannot quietly point at a column that has since been dropped.
- Delimiter. Applied to both files. If your baseline is comma separated and your current file is semicolon separated, convert one of them first; there is one delimiter setting, not two.
The four downloads, and what is in each
- Report (.txt). The fixed-width text you see on screen, saved as
<baseline name>-variance.txt. Headline block, the top fifteen drivers, up to ten new categories, up to ten disappeared ones, schema drift and up to twelve completeness rows. It is monospaced on purpose so the columns still line up when it is pasted into a ticket or a chat window. - Standalone HTML. One self-contained file,
<baseline name>-variance.html, with summary tiles, a driver table of up to 200 rows and colored up and down values. The styles are inline and there is nothing to fetch, so it opens from a network share or an email attachment. It carriesnoindex, nofollowand a no-referrer policy in the head, because the file holds somebody's numbers and should not turn up in a search index if it lands on a web server by accident. - Markdown. The same report as headings and pipe tables, saved as
<baseline name>-variance.md. Drop it into a wiki page, a pull request description or a repository and the tables render. - Driver CSV. Every category, not only the top fifteen, saved as
<baseline name>-drivers.csv. Nine columns: your breakdown column name,baseline,current,change,change_percent,share_of_net_change_percent,status,baseline_rowsandcurrent_rows. The two percentage columns carry four decimals and are left empty rather than filled in when there is no percentage to give. Status is one ofboth,neworgone. This is the file to pivot in a spreadsheet or feed to a chart.
The row-count columns are easy to overlook and often the most useful pair in the file. A category whose total held steady while its row count halved is a different story from one where both held steady, and only the driver CSV shows you that.
The saved setup holds no data
Rerunning the same comparison every month means retyping the same five choices every month. Type a name into Save this setup as and they are stored in this browser under the key emd-compare-setups. What gets written is a small object with six fields: a schema version, the name, the metric column name, the breakdown column name, the aggregate, and the two labels.
That list is the whole thing. No cell values, no totals, no row counts, no filenames, not even a timestamp. The stored record describes the shape of the question, never the answer or the data it came from, which means you can open your browser storage, copy the object out and paste it into a shared ticket without leaking a single figure. The run confirms it in the warnings, naming the setup and repeating what it contains.
Loading works the same way in reverse: type the name into Load a saved setup and the stored choices seed the run. A saved column name is only honored if both of today's files still have that column, so a setup that refers to a column somebody dropped last week falls back to the automatic pick instead of failing in a confusing way. If your browser refuses to write to local storage, which private windows often do, the run still completes and a warning says the setup was not saved rather than pretending it was.
Limits and refusals
- A metric that is not in both files stops the run. The error names the column and lists the shared columns so you can see what you actually had to choose from. Treating the missing side as zero would produce a hundred per cent decline that never happened.
- A breakdown column that is not in both files stops the run too. Categories cannot be lined up across a column one file does not have.
- Twenty thousand distinct categories is the ceiling. Counted across both files combined. Past it the run stops and prints the count. A ranking of twenty thousand rows is not a ranking, and hitting this cap almost always means an identifier got picked instead of a category.
- Two files are required. There is no single-file mode. Drop something into both boxes or the run refuses with a one-line explanation.
- A zero baseline yields no percentage. Not infinity, not a hundred per cent, not a dash that looks like a number. The percentage column reads
n/a, and when the headline baseline itself is zero the report adds two lines saying the absolute change is the whole story. - A net change of exactly zero leaves the share column empty. Categories can move in both directions and cancel out perfectly. Dividing by that zero would be meaningless, so the share of net column is blank while the individual changes are still shown in full.
- Columns with no blanks in either file are left out of the completeness table. A table of rows all reading zero to zero is noise. Only columns whose blank rate actually moved are listed.
The built-in example pair
Click Try an example and you get two different files, not the same file loaded twice. The baseline holds twelve rows of regional revenue by channel and product. The current file holds thirteen, and it differs in four ways worth reading: East is roughly flat, West falls hard on both orders and revenue, a Central region appears that was not there before, and the current file carries a discount_code column the baseline never had.
Run it with the metric on revenue and the breakdown on region and every section of the report has something in it. Then switch the breakdown to channel and watch the story change shape without a single number changing: the same movement is now attributed to Web, Retail and Partner instead. Neither view is more true than the other. Which one you choose is the analysis.
Frequently Asked Questions
Is this a row-by-row diff?
No. A diff matches rows and tells you that line 412 changed. This tool aggregates both files first, then subtracts. You pick a number to total up and a column whose values become the categories, and the output is a headline change plus a ranked list of the categories that moved it. If you want to know which individual records differ, a diff is the right tool; if you want to know why the total moved, this is.
What happens when a column is only in one of the two files?
It shows up under schema drift as added or removed, and it cannot be used as the metric or the breakdown. If you name a column that only one file has, the run stops with an error that lists the columns the two files do share, rather than treating the missing side as zero. Every column present in both is still compared for completeness.
Which download should I take?
There are four. Report (.txt) is the fixed-width text shown on screen, good for pasting into a ticket. Standalone HTML is a single self-contained file with tiles and tables, marked noindex and no-referrer because it carries your numbers. Markdown is the same report as headings and pipe tables, for a wiki or a pull request. Driver CSV is every category, not just the top fifteen, with nine columns including the signed share of the net change.
What exactly does a saved setup store?
Six things: a schema version, the name you typed, the metric column name, the breakdown column name, the aggregate, and the two labels. That is all. No cell values, no filenames, no row counts, no totals. It lives in this browser's local storage under the key emd-compare-setups, so it is safe to read out and paste into a ticket. Type a name into Save this setup as to write one, and the same name into Load a saved setup to bring it back.
Why is the percentage blank on some rows?
Because the baseline was zero and division by zero is not a percentage. A category that did not exist last month and billed 700 this month has grown by 700, and any percent figure next to that is invented. The tool prints n/a in the change % column instead. The same rule applies to the headline: if the baseline total is zero, the report says so in a sentence and leaves the absolute change to speak for itself.
Is there a limit on how many categories it will rank?
Twenty thousand distinct values across the two files combined. Past that the run stops and names the count, because a ranking with twenty thousand rows is a data dump and not an explanation. If you hit the cap you have almost certainly picked an identifier rather than a category: swap the order id for the region, the product line or the channel. There is no cap on rows, only on distinct categories.
Do both files get uploaded anywhere?
No. There is no upload endpoint on this page. Both files are read by JavaScript inside your tab, aggregated there, and the report is built there. The only thing written anywhere is the saved setup, which goes into your own browser storage and holds column names and labels. Reload the page and both drop zones are empty again.
Related
Put last period next to this one
Free, no account, no upload. Two files, one number, and a ranked list of what moved it.
Back to the comparison