Performance: 27× speedup across matchers + Coma accuracy improvements - #96
Merged
Merged
Conversation
…inor improvements
…nstalled The CI matrix runs `python -m unittest discover tests` without polars. `pytest.importorskip` at module level raises a Skipped exception that unittest treats as an ImportError, failing the entire run. Replace with try/except that guards all polars-dependent imports and data loading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The coverage CI job runs pytest without polars installed. The previous fix only handled unittest discover (import crash) but pytest still discovered the test classes and hit NameError on undefined symbols. Use @pytest.mark.skipif on each class so both runners skip gracefully. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously the polars tests were silently skipped in every CI job because polars was never installed. Add `pip install ".[polars]" || true` to the coverage job and all matrix test jobs. The `|| true` handles Python versions where polars doesn't ship a wheel yet (e.g. 3.14 pre-release) — the skip markers in test_polars.py handle that gracefully. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- docs/example.md: fix code block title to match renamed file - cupid/__init__.py: remove empty DATATYPE_COMPATIBILITY_TABLE (was kept as backwards-compat stub but is a silent-failure trap) - utils/utils.py: remove unused is_sorted function - test_utils.py: remove corresponding test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
chrisk21
reviewed
Apr 27, 2026
chrisk21
left a comment
Collaborator
There was a problem hiding this comment.
Mainly identified issues with the coma-py implementation, where a lot of choices right now seem tailored to specific test-cases; we need to abstract even at the expense of lower effectiveness results on the NYU benchmark. Some ad-hoc parts of the implementation have been also identified. We need to resolve these before merging.
Member
Author
Benchmark results after addressing review comments@chrisk21 Ran the full NYU benchmark after implementing all review feedback. Here's the summary. Changes applied
NYU benchmark — aggregate results
Coma comparison: before vs after (per-dataset)
Key takeaways
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves #88
Across-the-board performance and accuracy work on every matcher, plus robustness fixes, Polars support, dead-code removal, documentation updates, and comprehensive test coverage.
Headline: ~27× wall-clock speedup on the full NYU Open Data benchmark (1048s → 39s) with Coma accuracy improvements from targeted matcher simplification.
Performance: before / after
Full NYU Open Data benchmark, 10 dataset pairs, single machine, sequential.
JaccardDistanceMatcher F1 delta (−0.009) is within run-to-run noise. SimilarityFlooding is slightly slower due to the improved tokeniser and NodeID collision fix processing more tokens. Coma_Inst F1 delta (+0.007) shows the simplified matchers maintain accuracy while being much faster.
Coma per-dataset detail
The old "Coma" used
use_instances=Trueplus redundant matchers (LEAVES_CM, PARENTS_CM, PATH_CM). Coma (schema) is new — schema-only with simplified matchers. Coma_Inst is the new equivalent of the old Coma.What changed
Performance
TfidfCorpusbuilds float32 sparse CSR matrices, caches per-column vectorisations on object identity, and memoises pair-level similarities on a symmetric(id, id)key.InstancesCMevaluatesInstancesDirectandInstancesAllon the same list — the pair cache collapses both calls into one matmul.wn.synsetsandwn.wup_similarity(symmetric key), plus the English stopword frozenset and theall_lemma_namescorpus walk. The cold-path lemma walk used to dominate; it's now paid once per process.QuantileHistogram.add_valuesreplaces a Pythonbucket_binary_searchloop with a singlenp.searchsorted+np.bincountover precomputed lower/upper-bound arrays.__slots__on the histogram,lru_cacheon the constant_bucket_distance_matrix(n), and a global ranks pickle cache to avoid re-unpickling per column.rapidfuzz.process.cdist, dispatched to the smaller side (rows × cols favours small rows), withscore_cutoff=thresholdso rapidfuzz can short-circuit. This is the matcher that moved most in absolute time (−735 s).BaseTable.get_data_typenow treats pandas"str"/"string"dtypes as text, not as unknown. Free F1 wins for Cupid and SF (which readdata_type) and a prerequisite for the Coma accuracy work.Coma matcher simplification
Removed matchers that are redundant or constant in flat tabular schemas:
LEAVES_CM— for flat schemas,get_leaves(column)returns[column], making this identical toNAME_CM.PARENTS_CM—get_parents(column)returns[root], so this produces a constant score for every source-target pair.PATH_CM— trigram on"table column"where the shared table name prefix adds noise.SIBLINGS_CM— was defined but never used in any matcher list (dead code).DATATYPE_MATCHER— defined but never added to any ComplexMatcher (dead code).Result: Coma schema-only now uses only
NAME_CM(+ optionallyInstancesCM). This is 8.5x faster and removes score dilution from constant/redundant matchers. On DPR_AthleticFacilities (30 columns), schema-only F1 improved 0.571 → 0.778.Coma accuracy improvements
NameCM. Newtokens.pysplits column names intocamelCase/snake_case/ digit runs and computes a soft Dice-Sørensen coefficient with generic abbreviation matching (dept→department,fname→firstname,st→street,dr→drive). Usesmaximumwith trigram so it can only help.Coma(instance_weight=...)controls relative weight ofInstancesCMvs schema matchers (default:1.0= uniform). Previously hardcoded at1.3.Cupid improvements
varchar(255),bigint, etc.) via keyword matching.name_similarity_tokensreturns 0.0 when either token set is empty;compute_ssimreturns 0.0 when both nodes have empty leaf lists.Metrics
MeanReciprocalRank(MRR) — added tovalentine/metrics/. For each ground truth pair, finds its rank in the result list. MRR = mean of 1/rank across all GT pairs. Added toMETRICS_ALLandMETRICS_CORE.Robustness fixes
quantile_emdreturnsinfwhen histogram values sum to zero instead of dividing by zero.id()key to detectid()reuse after GC, preventing stale cache hits.NodeIDcollision. Replaced plain"NodeID"string prefix with null-byte sentinel"\x00NID"so columns named"NodeID*"don't collide with structural graph nodes._camel_case_splitnow handlessnake_case,SCREAMING_SNAKE, hyphens, and embedded digits.get_encodinghandles chardet returningNone;get_delimitercatchescsv.Snifferfailures on malformed input.Polars support
PolarsTable/PolarsColumn— newBaseTable/BaseColumnadapters invalentine/data_sources/polars/for Polars DataFrames. Install withpip install valentine[polars].valentine_match— pandas and Polars frames can be freely mixed in the same call. Detection viatype(obj).__module__without importing polars eagerly.test_polars.pyverifying PolarsTable properties, all matchers with Polars input, pandas↔Polars equivalence, and mixed-framework matching.|| truefallback for Python versions without polars wheels).Benchmark CI
bench.ymlnow runs--accuracy-onlyand blocks PRs on F1/match-count/MRR changes (no morecontinue-on-error). Timing is not gated (too noisy on CI runners).--update-baselineflag — when a change is intentional,python experiments/bench.py --quick --baseline experiments/bench_baseline.json --update-baselineregenerates the baseline.experiments/.Dead code removal
QuantileHistogram.bucket_binary_search,normalize_values,calc_dist_matrixSIBLINGS_CM,DATATYPE_MATCHER,LEAVES_CM,PARENTS_CM,PATH_CMctx_siblings,ctx_leaves,ctx_parents,ctx_selfpath,extract_datatype,extract_pathCOMA_OPT_MATCHERS,COMA_OPT_INST_MATCHERS,INSTANCES_CM(predefined)coma/similarity/datatype.pyimport in matchers (file kept but unused in pipeline)utils/is_sorted,cupid/DATATYPE_COMPATIBILITY_TABLEExperiments tried and rejected
NameCM— −0.037 F1, +87% time. WordNet's high-scoring false positives on common tokens win bidirectional selection.Test plan
pytest -q tests— 272 passedpython -m unittest discover tests— 65 passed🤖 Generated with Claude Code