splink 5.0.0.dev1__tar.gz → 5.0.0.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/.gitignore +3 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/PKG-INFO +7 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/README.md +2 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/pyproject.toml +57 -15
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/__init__.py +1 -1
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking.py +90 -34
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking_analysis.py +146 -76
- splink-5.0.0.dev2/splink/internals/charts.py +714 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/clustering.py +4 -4
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison.py +12 -11
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level.py +54 -33
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level_library.py +15 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_library.py +1 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_vector_values.py +38 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/completeness.py +14 -14
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/connected_components.py +1 -1
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/database_api.py +100 -58
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/datasets/splink_datasets.py +8 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/dialects.py +14 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/duckdb/database_api.py +6 -23
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/duckdb/dataframe.py +28 -7
- splink-5.0.0.dev2/splink/internals/duckdb/duckdb_helpers/__init__.py +3 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +17 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/em_training_session.py +48 -21
- splink-5.0.0.dev2/splink/internals/estimate_u.py +525 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/expectation_maximisation.py +21 -43
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +54 -13
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/find_brs_with_comparison_counts_below_threshold.py +7 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/input_column.py +7 -4
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/labelling_tool.py +2 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker.py +20 -50
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/evaluation.py +36 -23
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/inference.py +119 -21
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/misc.py +1 -1
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/table_management.py +58 -39
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/training.py +19 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/visualisations.py +72 -69
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/lower_id_on_lhs.py +7 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/m_from_labels.py +1 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/m_training.py +1 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/misc.py +76 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/optimise_cost_of_brs.py +3 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/pipeline.py +45 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/postgres/database_api.py +4 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/postgres/dataframe.py +11 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/profile_data.py +12 -6
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/realtime.py +2 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings.py +60 -34
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/spark/database_api.py +18 -13
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/spark/dataframe.py +31 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/spark/jar_location.py +2 -13
- splink-5.0.0.dev2/splink/internals/spark/spark_helpers/version.py +12 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/splink_dataframe.py +60 -3
- splink-5.0.0.dev2/splink/internals/splinkdataframe_utils.py +53 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/sqlite/database_api.py +50 -15
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/sqlite/dataframe.py +12 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/term_frequencies.py +135 -137
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/testing.py +8 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/vertically_concatenate.py +158 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/waterfall_chart.py +3 -3
- splink-5.0.0.dev1/splink/internals/charts.py +0 -474
- splink-5.0.0.dev1/splink/internals/estimate_u.py +0 -230
- splink-5.0.0.dev1/splink/internals/files/chart_defs/missingness.json +0 -80
- splink-5.0.0.dev1/splink/internals/sqlite/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/LICENSE +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/backends/duckdb.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/backends/postgres.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/backends/spark.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/backends/sqlite.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/blocking_analysis.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/clustering.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/comparison_level_library.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/comparison_library.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/datasets.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/exploratory.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/accuracy.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/block_from_labels.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking_rule_creator.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking_rule_creator_utils.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/cache_dict_with_logging.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/chunking.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/cluster_studio.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/column_expression.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_creator.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level_composition.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level_creator.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level_sql.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_vector_distribution.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/constants.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/cost_of_blocking_rules.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/databricks/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/databricks/enable_splink.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/datasets/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/datasets/metadata.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/datasets/utils.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/default_from_jsonschema.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/duckdb/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/edge_metrics.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/exceptions.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/comparator_score_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/completeness.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/match_weights_interactive_history.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/precision_recall.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/profile_data.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/roc.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/d3@7.8.5 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/vega@5.31.0 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/labelling_tool/slt.js +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/labelling_tool/template.j2 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/settings_jsonschema.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/spark_jars/scala-udf-similarity-0.1.2_spark3.x.jar +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/spark_jars/scala-udf-similarity-0.2.0_spark4.x.jar +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_cluster_studio/cluster_template.j2 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_comparison_viewer/template.j2 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/templates/single_chart_template.html +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/find_matches_to_new_records.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/graph_metrics.py +0 -0
- {splink-5.0.0.dev1/splink/internals/duckdb/duckdb_helpers → splink-5.0.0.dev2/splink/internals/linker_components}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/clustering.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/logging_messages.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/m_u_records_to_parameters.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/match_weights_histogram.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/one_to_one_clustering.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/parse_sql.py +0 -0
- {splink-5.0.0.dev1/splink/internals/linker_components → splink-5.0.0.dev2/splink/internals/postgres}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/predict.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_creator.py +0 -0
- {splink-5.0.0.dev1/splink/internals/postgres → splink-5.0.0.dev2/splink/internals/settings_validation}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_validation/settings_column_cleaner.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_validation/settings_validation_log_strings.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_validation/valid_types.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/similarity_analysis.py +0 -0
- {splink-5.0.0.dev1/splink/internals/settings_validation → splink-5.0.0.dev2/splink/internals/spark}/__init__.py +0 -0
- {splink-5.0.0.dev1/splink/internals/spark → splink-5.0.0.dev2/splink/internals/spark/spark_helpers}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/splink_comparison_viewer.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/sql_transform.py +0 -0
- {splink-5.0.0.dev1/splink/internals/spark/spark_helpers → splink-5.0.0.dev2/splink/internals/sqlite}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/unique_id_concat.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/unlinkables.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/validate_jsonschema.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/py.typed +0 -0
|
@@ -163,6 +163,8 @@ cython_debug/
|
|
|
163
163
|
|
|
164
164
|
*.parquet
|
|
165
165
|
*.csv
|
|
166
|
+
*.json
|
|
167
|
+
|
|
166
168
|
|
|
167
169
|
# html files opt-in only
|
|
168
170
|
*.html
|
|
@@ -177,5 +179,6 @@ cython_debug/
|
|
|
177
179
|
splink_db
|
|
178
180
|
splink_db_log
|
|
179
181
|
spark-warehouse
|
|
182
|
+
*.ddb*
|
|
180
183
|
|
|
181
184
|
scripts/pyspelling/dictionary.dic
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splink
|
|
3
|
-
Version: 5.0.0.
|
|
3
|
+
Version: 5.0.0.dev2
|
|
4
4
|
Summary: Fast probabilistic data linkage at scale
|
|
5
5
|
Project-URL: Homepage, https://github.com/moj-analytical-services/splink
|
|
6
6
|
Project-URL: Repository, https://github.com/moj-analytical-services/splink
|
|
@@ -13,16 +13,18 @@ Requires-Dist: altair>=5.0.1
|
|
|
13
13
|
Requires-Dist: duckdb>=0.9.2
|
|
14
14
|
Requires-Dist: igraph>=0.11.2
|
|
15
15
|
Requires-Dist: jinja2>=3.0.3
|
|
16
|
-
Requires-Dist: numpy>=1.19.3
|
|
17
|
-
Requires-Dist: pandas>=1.3.5
|
|
18
16
|
Requires-Dist: sqlglot>=17.6.0
|
|
19
17
|
Provides-Extra: postgres
|
|
20
18
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'postgres'
|
|
21
19
|
Requires-Dist: sqlalchemy>=2.0.0; extra == 'postgres'
|
|
20
|
+
Provides-Extra: pyarrow
|
|
21
|
+
Requires-Dist: pyarrow>=15.0.0; extra == 'pyarrow'
|
|
22
22
|
Provides-Extra: pyspark
|
|
23
23
|
Requires-Dist: pyspark>=3.5.0; extra == 'pyspark'
|
|
24
24
|
Provides-Extra: spark
|
|
25
25
|
Requires-Dist: pyspark>=3.5.0; extra == 'spark'
|
|
26
|
+
Provides-Extra: sqlite
|
|
27
|
+
Requires-Dist: rapidfuzz>=3.10.0; extra == 'sqlite'
|
|
26
28
|
Description-Content-Type: text/markdown
|
|
27
29
|
|
|
28
30
|
<p align="center">
|
|
@@ -194,6 +196,8 @@ To find the best place to ask a question, report a bug or get general advice, pl
|
|
|
194
196
|
|
|
195
197
|
🥇 Civil Service Awards 2025: Innovation category - [Winner](https://x.com/CSWnews/status/1998488787433979981)
|
|
196
198
|
|
|
199
|
+
🥇 Civil Service Awards 2025: The Excellence In Delivery Award was [won](https://www.civilserviceawards.com/winners-2025/) by a dashboard powered by Splink.
|
|
200
|
+
|
|
197
201
|
🥇 OpenUK Awards 2025: Open data category - [Winner](https://openuk.uk/awards/)
|
|
198
202
|
|
|
199
203
|
🥈 Civil Service Awards 2023: Best Use of Data, Science, and Technology - [Runner up](https://www.civilserviceawards.com/best-use-of-data-science-and-technology-award-2/)
|
|
@@ -167,6 +167,8 @@ To find the best place to ask a question, report a bug or get general advice, pl
|
|
|
167
167
|
|
|
168
168
|
🥇 Civil Service Awards 2025: Innovation category - [Winner](https://x.com/CSWnews/status/1998488787433979981)
|
|
169
169
|
|
|
170
|
+
🥇 Civil Service Awards 2025: The Excellence In Delivery Award was [won](https://www.civilserviceawards.com/winners-2025/) by a dashboard powered by Splink.
|
|
171
|
+
|
|
170
172
|
🥇 OpenUK Awards 2025: Open data category - [Winner](https://openuk.uk/awards/)
|
|
171
173
|
|
|
172
174
|
🥈 Civil Service Awards 2023: Best Use of Data, Science, and Technology - [Runner up](https://www.civilserviceawards.com/best-use-of-data-science-and-technology-award-2/)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "splink"
|
|
3
|
-
version = "5.0.0.
|
|
3
|
+
version = "5.0.0.dev2"
|
|
4
4
|
description = "Fast probabilistic data linkage at scale"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Robin Linacre", email = "robinlinacre@hotmail.com" },
|
|
@@ -14,19 +14,21 @@ requires-python = ">=3.9.0,<4.0.0"
|
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
license = "MIT"
|
|
16
16
|
dependencies = [
|
|
17
|
-
"pandas>=1.3.5",
|
|
18
17
|
"duckdb>=0.9.2",
|
|
19
18
|
"sqlglot>=17.6.0",
|
|
20
19
|
"altair>=5.0.1",
|
|
21
20
|
"Jinja2>=3.0.3",
|
|
22
|
-
"numpy>=1.19.3",
|
|
23
21
|
"igraph>=0.11.2",
|
|
24
22
|
]
|
|
25
23
|
|
|
26
24
|
[project.optional-dependencies]
|
|
27
25
|
pyspark = ["pyspark>=3.5.0"]
|
|
28
26
|
spark = ["pyspark>=3.5.0"]
|
|
27
|
+
# sqlite doesn't _require_ this functionality, but it's used if we want to use fuzzy levels
|
|
28
|
+
sqlite = ["rapidfuzz>=3.10.0"]
|
|
29
29
|
postgres = ["sqlalchemy>=2.0.0", "psycopg2-binary>=2.9.0"]
|
|
30
|
+
pyarrow = ["pyarrow>=15.0.0"]
|
|
31
|
+
# TODO: pandas optional
|
|
30
32
|
|
|
31
33
|
[project.urls]
|
|
32
34
|
Homepage = "https://github.com/moj-analytical-services/splink"
|
|
@@ -35,18 +37,24 @@ Repository = "https://github.com/moj-analytical-services/splink"
|
|
|
35
37
|
[dependency-groups]
|
|
36
38
|
# allows us to easily check package functionality with just these installed
|
|
37
39
|
# (and not the rest of our required dependencies)
|
|
38
|
-
core = ["sqlglot", "
|
|
40
|
+
core = ["sqlglot", "duckdb"]
|
|
39
41
|
demos = [
|
|
40
42
|
"ipywidgets>=8.1.7",
|
|
43
|
+
"jupytext>=1.18.1",
|
|
41
44
|
"nbmake>=1.5.5",
|
|
45
|
+
"pseudopeople>=1.2.6",
|
|
42
46
|
"pytest-xdist>=3.6.1",
|
|
43
47
|
]
|
|
48
|
+
# dev deliberately doesn't include spark, so we can opt in to different spark versions
|
|
44
49
|
dev = [
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
50
|
+
{ "include-group" = "linting" },
|
|
51
|
+
{ "include-group" = "testing-core" },
|
|
52
|
+
{ "include-group" = "typechecking" },
|
|
53
|
+
]
|
|
54
|
+
spark-3 = [
|
|
55
|
+
"pyspark<4; python_version < '3.12'",
|
|
49
56
|
]
|
|
57
|
+
spark-4 = ["pyspark>=4.0.0"]
|
|
50
58
|
docs = [
|
|
51
59
|
"griffe>=1.4.0",
|
|
52
60
|
# keep jinja2 relaxed so it doesn't constrain us in rest of dev
|
|
@@ -65,24 +73,49 @@ docs = [
|
|
|
65
73
|
"pymdown-extensions>=10.15",
|
|
66
74
|
]
|
|
67
75
|
linting = ["ruff>=0.4.2,<0.5"]
|
|
68
|
-
|
|
76
|
+
pandas = ["pandas>=1.3.5", "numpy>=1.19.3"]
|
|
77
|
+
postgres = [
|
|
78
|
+
"psycopg2-binary>=2.9.0",
|
|
79
|
+
"sqlalchemy>=2.0.0",
|
|
80
|
+
]
|
|
81
|
+
sqlite = [
|
|
82
|
+
"rapidfuzz>=3.10.0",
|
|
83
|
+
]
|
|
84
|
+
testing-core = [
|
|
69
85
|
"pytest>=7.3",
|
|
70
|
-
"pyarrow>=
|
|
86
|
+
"pyarrow>=15.0.0",
|
|
71
87
|
"networkx>=2.5.1",
|
|
72
|
-
"rapidfuzz>=3.0.0",
|
|
73
88
|
"pytest-cov>=5.0.0",
|
|
89
|
+
{ "include-group" = "postgres" },
|
|
90
|
+
{ "include-group" = "sqlite" },
|
|
91
|
+
# TODO: temporary measure until we can depanda tests
|
|
92
|
+
{ "include-group" = "pandas" },
|
|
93
|
+
]
|
|
94
|
+
testing = [
|
|
95
|
+
{ "include-group" = "testing-core" },
|
|
96
|
+
{ "include-group" = "spark-4" },
|
|
74
97
|
]
|
|
75
98
|
typechecking = ["mypy==1.9.0"]
|
|
99
|
+
# we will migrate to ty, but won't happen all at once, so run in parallel
|
|
100
|
+
typechecking-dev = [
|
|
101
|
+
"ty>=0.0.21",
|
|
102
|
+
]
|
|
76
103
|
|
|
77
104
|
[tool.uv]
|
|
78
105
|
default-groups = [
|
|
79
|
-
"core",
|
|
80
106
|
"dev",
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
"spark-4"
|
|
108
|
+
]
|
|
109
|
+
conflicts = [
|
|
110
|
+
[
|
|
111
|
+
{ group = "spark-4" },
|
|
112
|
+
{ group = "spark-3" },
|
|
113
|
+
]
|
|
84
114
|
]
|
|
85
115
|
|
|
116
|
+
[tool.uv.dependency-groups]
|
|
117
|
+
docs = { requires-python = ">=3.13" }
|
|
118
|
+
|
|
86
119
|
[build-system]
|
|
87
120
|
requires = ["hatchling"]
|
|
88
121
|
build-backend = "hatchling.build"
|
|
@@ -115,6 +148,13 @@ lint.ignore = [
|
|
|
115
148
|
"B006", # Do not use mutable data structures for argument defaults"
|
|
116
149
|
]
|
|
117
150
|
|
|
151
|
+
# really we want to lint docs with a looser set of rules (such as not enforcing the same line length)
|
|
152
|
+
# for now we simply exclude
|
|
153
|
+
exclude = [
|
|
154
|
+
"docs",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
|
|
118
158
|
[tool.pytest.ini_options]
|
|
119
159
|
addopts = ["-m default"]
|
|
120
160
|
markers = [
|
|
@@ -132,6 +172,8 @@ markers = [
|
|
|
132
172
|
"sqlite_only",
|
|
133
173
|
"postgres",
|
|
134
174
|
"postgres_only",
|
|
175
|
+
# optional dependendencies beyond backends
|
|
176
|
+
"needs_pandas",
|
|
135
177
|
]
|
|
136
178
|
|
|
137
179
|
[tool.mypy]
|
|
@@ -14,12 +14,13 @@ from splink.internals.chunking import _chunk_assignment_sql
|
|
|
14
14
|
from splink.internals.database_api import DatabaseAPISubClass
|
|
15
15
|
from splink.internals.dialects import SplinkDialect
|
|
16
16
|
from splink.internals.input_column import InputColumn
|
|
17
|
-
from splink.internals.misc import ensure_is_list
|
|
17
|
+
from splink.internals.misc import dedupe_preserving_order, ensure_is_list
|
|
18
|
+
from splink.internals.parse_sql import parse_columns_in_sql
|
|
18
19
|
from splink.internals.pipeline import CTEPipeline
|
|
19
20
|
from splink.internals.splink_dataframe import SplinkDataFrame
|
|
20
21
|
from splink.internals.unique_id_concat import _composite_unique_id_from_nodes_sql
|
|
21
22
|
from splink.internals.vertically_concatenate import (
|
|
22
|
-
|
|
23
|
+
select_two_dataset_link_only_input_tables_sqls,
|
|
23
24
|
vertically_concatenate_sql,
|
|
24
25
|
)
|
|
25
26
|
|
|
@@ -76,6 +77,24 @@ def combine_unique_id_input_columns(
|
|
|
76
77
|
return unique_id_input_columns
|
|
77
78
|
|
|
78
79
|
|
|
80
|
+
def _columns_needed_for_blocking(
|
|
81
|
+
blocking_rules: List["BlockingRule"],
|
|
82
|
+
source_dataset_input_column: Optional[InputColumn],
|
|
83
|
+
unique_id_input_column: InputColumn,
|
|
84
|
+
) -> List[InputColumn]:
|
|
85
|
+
input_columns = combine_unique_id_input_columns(
|
|
86
|
+
source_dataset_input_column, unique_id_input_column
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
for br in blocking_rules:
|
|
90
|
+
parsed_columns = parse_columns_in_sql(
|
|
91
|
+
br.blocking_rule_sql, sqlglot_dialect=br.sqlglot_dialect
|
|
92
|
+
)
|
|
93
|
+
input_columns.extend(br._input_column(col.name) for col in parsed_columns)
|
|
94
|
+
|
|
95
|
+
return dedupe_preserving_order(input_columns)
|
|
96
|
+
|
|
97
|
+
|
|
79
98
|
class BlockingRule:
|
|
80
99
|
def __init__(
|
|
81
100
|
self,
|
|
@@ -319,6 +338,8 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
319
338
|
unique_id_input_column: InputColumn,
|
|
320
339
|
br: BlockingRule,
|
|
321
340
|
link_type: "LinkTypeLiteralType",
|
|
341
|
+
input_tablename_l: str,
|
|
342
|
+
input_tablename_r: str,
|
|
322
343
|
left_chunk: tuple[int, int] | None = None,
|
|
323
344
|
right_chunk: tuple[int, int] | None = None,
|
|
324
345
|
) -> str:
|
|
@@ -349,11 +370,6 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
349
370
|
id_expr_l = _composite_unique_id_from_nodes_sql(unique_id_input_columns, "l")
|
|
350
371
|
id_expr_r = _composite_unique_id_from_nodes_sql(unique_id_input_columns, "r")
|
|
351
372
|
|
|
352
|
-
if link_type == "two_dataset_link_only":
|
|
353
|
-
where_condition = (
|
|
354
|
-
where_condition + " and l.source_dataset < r.source_dataset"
|
|
355
|
-
)
|
|
356
|
-
|
|
357
373
|
exclude_sql = self.exclude_pairs_generated_by_all_preceding_rules_sql(
|
|
358
374
|
source_dataset_input_column, unique_id_input_column
|
|
359
375
|
)
|
|
@@ -361,8 +377,8 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
361
377
|
select distinct
|
|
362
378
|
{id_expr_l} as {unique_id_col.name_l},
|
|
363
379
|
{id_expr_r} as {unique_id_col.name_r}
|
|
364
|
-
from
|
|
365
|
-
inner join
|
|
380
|
+
from {input_tablename_l} as l
|
|
381
|
+
inner join {input_tablename_r} as r
|
|
366
382
|
on ({br.blocking_rule_sql})
|
|
367
383
|
{where_condition}
|
|
368
384
|
{exclude_sql}
|
|
@@ -468,34 +484,65 @@ def materialise_exploded_id_tables(
|
|
|
468
484
|
return []
|
|
469
485
|
exploded_tables = []
|
|
470
486
|
|
|
471
|
-
pipeline = CTEPipeline()
|
|
472
|
-
|
|
473
|
-
sql = vertically_concatenate_sql(
|
|
474
|
-
splink_df_dict, source_dataset_input_column=source_dataset_input_column
|
|
475
|
-
)
|
|
476
|
-
pipeline.enqueue_sql(sql, "__splink__df_concat")
|
|
477
|
-
nodes_concat = db_api.sql_pipeline_to_splink_dataframe(pipeline)
|
|
478
|
-
|
|
479
|
-
input_columns_set = set(nodes_concat.columns)
|
|
480
|
-
|
|
481
487
|
for br in exploding_blocking_rules:
|
|
482
|
-
pipeline = CTEPipeline(
|
|
488
|
+
pipeline = CTEPipeline()
|
|
483
489
|
arrays_to_explode_cols = [
|
|
484
490
|
br._input_column(colname) for colname in br.array_columns_to_explode
|
|
485
491
|
]
|
|
492
|
+
input_columns = _columns_needed_for_blocking(
|
|
493
|
+
[*br.preceding_rules, br],
|
|
494
|
+
source_dataset_input_column=source_dataset_input_column,
|
|
495
|
+
unique_id_input_column=unique_id_input_column,
|
|
496
|
+
)
|
|
497
|
+
other_cols = [col for col in input_columns if col not in arrays_to_explode_cols]
|
|
486
498
|
|
|
487
|
-
|
|
499
|
+
if link_type == "two_dataset_link_only":
|
|
500
|
+
if source_dataset_input_column is None:
|
|
501
|
+
raise ValueError(
|
|
502
|
+
"source_dataset_input_column is required for two_dataset_link_only"
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
left_sql, right_sql = select_two_dataset_link_only_input_tables_sqls(
|
|
506
|
+
splink_df_dict,
|
|
507
|
+
input_columns=input_columns,
|
|
508
|
+
source_dataset_input_column=source_dataset_input_column,
|
|
509
|
+
)
|
|
488
510
|
|
|
489
|
-
|
|
490
|
-
"
|
|
491
|
-
br.array_columns_to_explode,
|
|
492
|
-
[col.name for col in other_cols],
|
|
493
|
-
)
|
|
511
|
+
input_tablename_l = "__splink__df_concat_left_unnested"
|
|
512
|
+
input_tablename_r = "__splink__df_concat_right_unnested"
|
|
494
513
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
514
|
+
expl_sql_l = db_api.sql_dialect.explode_arrays_sql(
|
|
515
|
+
f"({left_sql})",
|
|
516
|
+
br.array_columns_to_explode,
|
|
517
|
+
[col.name for col in other_cols],
|
|
518
|
+
)
|
|
519
|
+
pipeline.enqueue_sql(expl_sql_l, input_tablename_l)
|
|
520
|
+
|
|
521
|
+
expl_sql_r = db_api.sql_dialect.explode_arrays_sql(
|
|
522
|
+
f"({right_sql})",
|
|
523
|
+
br.array_columns_to_explode,
|
|
524
|
+
[col.name for col in other_cols],
|
|
525
|
+
)
|
|
526
|
+
pipeline.enqueue_sql(expl_sql_r, input_tablename_r)
|
|
527
|
+
else:
|
|
528
|
+
sql = vertically_concatenate_sql(
|
|
529
|
+
splink_df_dict, source_dataset_input_column=source_dataset_input_column
|
|
530
|
+
)
|
|
531
|
+
pipeline.enqueue_sql(sql, "__splink__df_concat")
|
|
532
|
+
|
|
533
|
+
input_tablename_l = "__splink__df_concat_unnested"
|
|
534
|
+
input_tablename_r = "__splink__df_concat_unnested"
|
|
535
|
+
|
|
536
|
+
expl_sql = db_api.sql_dialect.explode_arrays_sql(
|
|
537
|
+
"__splink__df_concat",
|
|
538
|
+
br.array_columns_to_explode,
|
|
539
|
+
[col.name for col in other_cols],
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
pipeline.enqueue_sql(
|
|
543
|
+
expl_sql,
|
|
544
|
+
input_tablename_l,
|
|
545
|
+
)
|
|
499
546
|
|
|
500
547
|
base_name = "__splink__marginal_exploded_ids_blocking_rule"
|
|
501
548
|
table_name = f"{base_name}_mk_{br.match_key}"
|
|
@@ -505,6 +552,8 @@ def materialise_exploded_id_tables(
|
|
|
505
552
|
unique_id_input_column=unique_id_input_column,
|
|
506
553
|
br=br,
|
|
507
554
|
link_type=link_type,
|
|
555
|
+
input_tablename_l=input_tablename_l,
|
|
556
|
+
input_tablename_r=input_tablename_r,
|
|
508
557
|
left_chunk=left_chunk,
|
|
509
558
|
right_chunk=right_chunk,
|
|
510
559
|
)
|
|
@@ -559,11 +608,18 @@ def compute_blocked_pairs_from_concat_with_tf(
|
|
|
559
608
|
"source_dataset column"
|
|
560
609
|
)
|
|
561
610
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
source_dataset_input_column
|
|
611
|
+
input_columns = _columns_needed_for_blocking(
|
|
612
|
+
blocking_rules=blocking_rules,
|
|
613
|
+
source_dataset_input_column=source_dataset_input_column,
|
|
614
|
+
unique_id_input_column=unique_id_input_column,
|
|
615
|
+
)
|
|
616
|
+
left_sql, right_sql = select_two_dataset_link_only_input_tables_sqls(
|
|
617
|
+
splink_df_dict,
|
|
618
|
+
input_columns=input_columns,
|
|
619
|
+
source_dataset_input_column=source_dataset_input_column,
|
|
565
620
|
)
|
|
566
|
-
pipeline.
|
|
621
|
+
pipeline.enqueue_sql(left_sql, f"{df_concat_with_tf_table_name}_left")
|
|
622
|
+
pipeline.enqueue_sql(right_sql, f"{df_concat_with_tf_table_name}_right")
|
|
567
623
|
|
|
568
624
|
blocking_input_tablename_l = f"{df_concat_with_tf_table_name}_left"
|
|
569
625
|
blocking_input_tablename_r = f"{df_concat_with_tf_table_name}_right"
|