INSTRUMENT | Eval statistics
Near-Duplicate Row Detector: Find Eval Rows That Differ Only in Formatting
5 cited sources
Find rows in an eval set that are the same test written twice. Normalize the columns you pick, group rows whose normalized text matches, and read the exact transform, the raw variants, and a CSV.
Two rows can look different and test the same thing. Case, a stray question mark, a non-breaking space, or a full-width character hides a copy that weights one case twice in every number an eval reports.
This page normalizes the columns you pick, groups rows whose normalized text matches, and prints the transform, which keeps a result reproducible. It reads characters, never meaning. Byte-identical repeats belong to the exact-duplicate checker, and the file's shape to the schema validator.
Showing your last valid result. Update the inputs above to recompute.
Near-duplicate clusters
1
1 near-duplicate cluster over 7 rows considered. Seven-row example, standard preset, on the column "input".
- Rows considered
- 7
- Rows in clusters
- 5
- Near-duplicate rate
- 71.4%
- Exact-duplicate rows counted here, reported by the exact-duplicate check
- 1
- Rows skipped for an empty key
- 0
A row counts once. The rate is rows in clusters over rows considered, to one decimal.
1 cluster holds 5 of 7 rows considered.
Each cluster states the text its members normalized to, then lists them.
| Cluster | Row | Line | Raw text | Merged at | Split |
|---|---|---|---|---|---|
| 1 | 5 rows, normalized to: what is the capital of france | ||||
| 1 | What is the capital of France? | first member | |||
| 2 | what is the capital of France? | L4 | |||
| 3 | What is the capital of France ? | L10 | |||
| 5 | What is the capital of France? | exact duplicate | |||
| 7 | What is the capital of France | L8 |
key(row) = ladder(text) per column, then one hash passHow?
How this is calculated
Every selected column is normalized on its own through the steps you leave on, in the fixed order below. The normalized values become one row key through a length-prefixed, type-tagged encoder, never a joined string: a separator inside a joined string is itself normalizable, so two rows carrying a b and c would collide with two carrying a and b c. Rows are then grouped by that key in a single pass. There is no pairwise comparison anywhere on this page, which is why 200,000 rows cost one pass rather than twenty billion.
A group is reported only when it holds two or more rows and two or more distinct raw keys, where a raw key trims each value at both ends and compares the rest byte for byte. A group whose raw keys are all identical is a byte-exact repeat: it is counted under exact-duplicate rows and reported by the exact-duplicate check, not here, so no pair is reported twice.
| Step | What it does | Default | Source |
|---|---|---|---|
| L1 | s.normalize('NFC'). Not switchable. | always on | UAX #15 |
| L2 | Remove every format character; replace every control character with a space. | on | W3C charmod-norm |
| L3 | s.normalize('NFKC'). Carries its own warning: it merges rows that mean different things. | on | UAX #15 |
| L4 | The CaseFolding.txt C plus F table, 1,585 entries, applied directly and then re-normalized to NFC. No toLowerCase call anywhere. | on | Unicode case-mapping FAQ |
| L5 | A dated table of 77 Cyrillic and Greek letters that look like ASCII. | off | UTS #39 |
| L6 | NFD, drop every non-spacing mark, back to NFC. | off | UAX #15 |
| L7 | Curly quotes and primes to the two ASCII quotes, every dash to a hyphen, an ellipsis to three periods. | on | OpenRefine fingerprint |
| L8 | Remove every punctuation character. | on | OpenRefine fingerprint |
| L8b | Remove every symbol character. | off | OpenRefine fingerprint |
| L9 | Every run of digits becomes a single zero. | off | this page |
| L10 | Every run of whitespace becomes one space, then trim. | on | OpenRefine fingerprint |
| L11 | Split on a space, sort by code unit, drop repeats, rejoin. | off | OpenRefine fingerprint |
The ladder is OpenRefine's key-collision fingerprint with the Unicode handling made explicit. Each Unicode and W3C document named in that table is linked in full under Sources below.
Case-folding table: Unicode 17.0.0, 1,585 entries, read 2026-08-23. Confusable table: Unicode 17.0.0, 77 entries, read 2026-08-23.
A row number is the data row with any header excluded. A line number is the file line the row starts on, which stops matching once a quoted field spans two lines. Counts are whole numbers and the rate is rounded to one decimal, half up. At most 20 clusters and 8 rows per cluster render, and a long cell is clipped on screen. The CSV carries every cluster, every member, and the full text of every cell.
Formula: key(row) = ladder(text) per column, then one hash pass
Questions
Why case folding rather than lowercasing?
Lowercasing is a display operation and it is not symmetric. The Greek capital sigma lowercases to a final sigma at the end of a word and to a medial sigma elsewhere, so a capitalized Greek word and the same word already in lowercase come out different and stay in two clusters. Case folding is defined for comparison: it sends both sigmas to one letter, it turns the German sharp s into a double s, and it is language-neutral, so the answer does not change with the language settings on your machine.
This page applies the published folding table one code point at a time and never calls a lowercase function, so no lowercase special case can interact with it. That is what keeps a Turkish dotted capital I from producing one answer on your machine and another on a colleague's.
Why is compatibility folding on by default when the W3C advises against it?
The W3C character model says compatibility normalization should not be applied unless it was explicitly asked for, and that is sound advice for matching text on the open web, where a false match is silent and permanent. The trade here runs the other way. This page never edits your file. It prints the transform it used, shows every raw variant beside what it normalized to, and turns the step off with one click. A false merge costs you a glance at two rows; a missed merge is the thing you came here to find.
The step still carries its own warning, above, naming the cases it merges. If your set is full of chemical formulas or exponents, turn it off first.
What does this not catch?
Anything that needs to read meaning. Two rows that ask the same question in different words keep different keys here no matter which switches you set, because every step is a character transform. Paraphrase detection needs an embedding model, and this page loads none.
It also does not catch partial overlap, where one row quotes half of another. That is graded similarity, measured with n-gram overlap rather than exact key equality, and it belongs to a different rung.
Does my file go anywhere?
No. The file is read, parsed, normalized, and grouped in this tab. There is no upload, no server call, and no analytics payload that carries a file name, a size, a row count, a column name, a key, or a cell value. The export is built in the browser from what is already on this page.
Why is there no similarity slider?
Because a threshold you can drag is a threshold nobody can reproduce. The measure here is binary by construction: two rows share a key or they do not, and the whole transform that produced the key is printed above the table and carried in the export. Anyone can redo it by hand and get the same clusters. A slider would replace that with a number whose meaning depends on which library computed it.
Sources
- UAX #15, Unicode Normalization Forms: the NFC and NFKC definitions the L1 and L3 rungs run, and the warning that compatibility forms erase distinctions
- Unicode case mapping and case folding FAQ: why case folding is language-neutral, is for comparison only, and is not the same operation as lowercasing
- UTS #39, Unicode Security Mechanisms: the skeleton method behind the confusable-letter rung, and its own warning that the data is not stable across versions
- W3C Character Model for the World Wide Web, String Matching: when compatibility normalization is not appropriate, which is why the NFKC rung is disclosed and reversible
- Deduplicating Training Data Makes Language Models Better: the edit-similarity and MinHash thresholds the next rung up uses, and the measured cost of leaving duplicates in