splink 5.0.0.dev1__tar.gz → 5.0.0.dev3__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.dev3}/.gitignore +3 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/PKG-INFO +7 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/README.md +2 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/pyproject.toml +57 -15
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/__init__.py +1 -1
- splink-5.0.0.dev3/splink/backends/duckdb.py +6 -0
- splink-5.0.0.dev3/splink/backends/spark.py +5 -0
- splink-5.0.0.dev3/splink/exploratory.py +4 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/accuracy.py +6 -11
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/blocking.py +103 -35
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/blocking_analysis.py +164 -90
- splink-5.0.0.dev3/splink/internals/charts.py +714 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/clustering.py +6 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison.py +19 -17
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_level.py +73 -49
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_level_library.py +15 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_library.py +1 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_vector_values.py +55 -19
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/completeness.py +16 -15
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/connected_components.py +3 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/database_api.py +101 -70
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/datasets/splink_datasets.py +8 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/dialects.py +14 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/duckdb/database_api.py +6 -23
- splink-5.0.0.dev3/splink/internals/duckdb/database_api_with_profiling.py +71 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/duckdb/dataframe.py +28 -7
- splink-5.0.0.dev3/splink/internals/duckdb/duckdb_helpers/__init__.py +3 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +17 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/em_training_session.py +57 -27
- splink-5.0.0.dev3/splink/internals/estimate_u.py +565 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/expectation_maximisation.py +26 -47
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +54 -13
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/find_brs_with_comparison_counts_below_threshold.py +7 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/input_column.py +7 -4
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/labelling_tool.py +4 -7
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/linker.py +28 -58
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/linker_components/evaluation.py +36 -23
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/linker_components/inference.py +171 -91
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/linker_components/misc.py +1 -1
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/linker_components/table_management.py +58 -80
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/linker_components/training.py +24 -17
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/linker_components/visualisations.py +72 -69
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/lower_id_on_lhs.py +7 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/m_from_labels.py +3 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/m_training.py +9 -11
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/misc.py +101 -10
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/optimise_cost_of_brs.py +3 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/pipeline.py +52 -9
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/postgres/database_api.py +4 -5
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/postgres/dataframe.py +11 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/predict.py +34 -19
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/profile_data.py +15 -9
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/realtime.py +2 -2
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/settings.py +60 -34
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/settings_validation/settings_column_cleaner.py +3 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/spark/database_api.py +18 -13
- splink-5.0.0.dev3/splink/internals/spark/database_api_with_profiling.py +174 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/spark/dataframe.py +31 -3
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/spark/jar_location.py +2 -13
- splink-5.0.0.dev3/splink/internals/spark/spark_helpers/version.py +12 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/splink_dataframe.py +61 -3
- splink-5.0.0.dev3/splink/internals/splinkdataframe_utils.py +53 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/sqlite/database_api.py +50 -15
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/sqlite/dataframe.py +12 -2
- splink-5.0.0.dev3/splink/internals/term_frequencies.py +286 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/testing.py +8 -3
- splink-5.0.0.dev3/splink/internals/vertically_concatenate.py +308 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/waterfall_chart.py +3 -3
- splink-5.0.0.dev1/splink/backends/duckdb.py +0 -3
- splink-5.0.0.dev1/splink/backends/spark.py +0 -4
- splink-5.0.0.dev1/splink/exploratory.py +0 -5
- splink-5.0.0.dev1/splink/internals/charts.py +0 -474
- splink-5.0.0.dev1/splink/internals/default_from_jsonschema.py +0 -20
- 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/internals/term_frequencies.py +0 -347
- splink-5.0.0.dev1/splink/internals/validate_jsonschema.py +0 -44
- splink-5.0.0.dev1/splink/internals/vertically_concatenate.py +0 -234
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/LICENSE +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/backends/postgres.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/backends/sqlite.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/blocking_analysis.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/clustering.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/comparison_level_library.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/comparison_library.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/datasets.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/block_from_labels.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/blocking_rule_creator.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/blocking_rule_creator_utils.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/cache_dict_with_logging.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/chunking.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/cluster_studio.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/column_expression.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_creator.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_level_composition.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_level_creator.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_level_sql.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/comparison_vector_distribution.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/constants.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/cost_of_blocking_rules.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/databricks/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/databricks/enable_splink.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/datasets/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/datasets/metadata.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/datasets/utils.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/duckdb/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/edge_metrics.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/exceptions.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/comparator_score_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/completeness.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/match_weights_interactive_history.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/precision_recall.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/profile_data.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/roc.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/external_js/d3@7.8.5 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/external_js/vega@5.31.0 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/labelling_tool/slt.js +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/labelling_tool/template.j2 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/settings_jsonschema.json +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/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.dev3}/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.dev3}/splink/internals/files/splink_cluster_studio/cluster_template.j2 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/splink_comparison_viewer/template.j2 +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/files/templates/single_chart_template.html +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/find_matches_to_new_records.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/graph_metrics.py +0 -0
- {splink-5.0.0.dev1/splink/internals/duckdb/duckdb_helpers → splink-5.0.0.dev3/splink/internals/linker_components}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/linker_components/clustering.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/logging_messages.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/m_u_records_to_parameters.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/match_weights_histogram.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/one_to_one_clustering.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/parse_sql.py +0 -0
- {splink-5.0.0.dev1/splink/internals/linker_components → splink-5.0.0.dev3/splink/internals/postgres}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/settings_creator.py +0 -0
- {splink-5.0.0.dev1/splink/internals/postgres → splink-5.0.0.dev3/splink/internals/settings_validation}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/settings_validation/settings_validation_log_strings.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/settings_validation/valid_types.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/similarity_analysis.py +0 -0
- {splink-5.0.0.dev1/splink/internals/settings_validation → splink-5.0.0.dev3/splink/internals/spark}/__init__.py +0 -0
- {splink-5.0.0.dev1/splink/internals/spark → splink-5.0.0.dev3/splink/internals/spark/spark_helpers}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/splink_comparison_viewer.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/sql_transform.py +0 -0
- {splink-5.0.0.dev1/splink/internals/spark/spark_helpers → splink-5.0.0.dev3/splink/internals/sqlite}/__init__.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/unique_id_concat.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/splink/internals/unlinkables.py +0 -0
- {splink-5.0.0.dev1 → splink-5.0.0.dev3}/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.dev3
|
|
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.dev3"
|
|
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]
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from splink.internals.spark.database_api import SparkAPI
|
|
2
|
+
from splink.internals.spark.database_api_with_profiling import SparkAPIWithProfiling
|
|
3
|
+
from splink.internals.spark.jar_location import similarity_jar_location
|
|
4
|
+
|
|
5
|
+
__all__ = ["similarity_jar_location", "SparkAPI", "SparkAPIWithProfiling"]
|
|
@@ -14,8 +14,8 @@ from splink.internals.predict import predict_from_comparison_vectors_sqls_using_
|
|
|
14
14
|
from splink.internals.splink_dataframe import SplinkDataFrame
|
|
15
15
|
from splink.internals.sql_transform import move_l_r_table_prefix_to_column_suffix
|
|
16
16
|
from splink.internals.vertically_concatenate import (
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
enqueue_df_concat,
|
|
18
|
+
enqueue_df_concat_with_tf,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -316,9 +316,7 @@ def truth_space_table_from_labels_table(
|
|
|
316
316
|
match_weight_round_to_nearest: Optional[float] = None,
|
|
317
317
|
) -> SplinkDataFrame:
|
|
318
318
|
pipeline = CTEPipeline()
|
|
319
|
-
|
|
320
|
-
nodes_with_tf = compute_df_concat_with_tf(linker, pipeline)
|
|
321
|
-
pipeline = CTEPipeline([nodes_with_tf])
|
|
319
|
+
enqueue_df_concat_with_tf(linker, pipeline)
|
|
322
320
|
|
|
323
321
|
sqls = predictions_from_sample_of_pairwise_labels_sql(linker, labels_tablename)
|
|
324
322
|
pipeline.enqueue_list_of_sqls(sqls)
|
|
@@ -351,13 +349,11 @@ def truth_space_table_from_labels_column(
|
|
|
351
349
|
group_by_statement = "group by source_dataset"
|
|
352
350
|
|
|
353
351
|
pipeline = CTEPipeline()
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
pipeline = CTEPipeline([concat])
|
|
352
|
+
enqueue_df_concat(linker, pipeline)
|
|
357
353
|
|
|
358
354
|
sql = f"""
|
|
359
355
|
select count(*) as count
|
|
360
|
-
from
|
|
356
|
+
from __splink__df_concat
|
|
361
357
|
{group_by_statement}
|
|
362
358
|
"""
|
|
363
359
|
|
|
@@ -451,8 +447,7 @@ def prediction_errors_from_labels_table(
|
|
|
451
447
|
threshold_match_probability: float = 0.5,
|
|
452
448
|
) -> SplinkDataFrame:
|
|
453
449
|
pipeline = CTEPipeline()
|
|
454
|
-
|
|
455
|
-
pipeline = CTEPipeline([nodes_with_tf])
|
|
450
|
+
enqueue_df_concat_with_tf(linker, pipeline)
|
|
456
451
|
|
|
457
452
|
sqls = predictions_from_sample_of_pairwise_labels_sql(linker, labels_tablename)
|
|
458
453
|
|
|
@@ -14,12 +14,17 @@ 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
|
|
17
|
+
from splink.internals.misc import (
|
|
18
|
+
dedupe_preserving_order,
|
|
19
|
+
ensure_is_list,
|
|
20
|
+
join_sql_with_union_all,
|
|
21
|
+
)
|
|
22
|
+
from splink.internals.parse_sql import parse_columns_in_sql
|
|
18
23
|
from splink.internals.pipeline import CTEPipeline
|
|
19
24
|
from splink.internals.splink_dataframe import SplinkDataFrame
|
|
20
25
|
from splink.internals.unique_id_concat import _composite_unique_id_from_nodes_sql
|
|
21
26
|
from splink.internals.vertically_concatenate import (
|
|
22
|
-
|
|
27
|
+
select_two_dataset_link_only_input_tables_sqls,
|
|
23
28
|
vertically_concatenate_sql,
|
|
24
29
|
)
|
|
25
30
|
|
|
@@ -76,6 +81,24 @@ def combine_unique_id_input_columns(
|
|
|
76
81
|
return unique_id_input_columns
|
|
77
82
|
|
|
78
83
|
|
|
84
|
+
def _columns_needed_for_blocking(
|
|
85
|
+
blocking_rules: List["BlockingRule"],
|
|
86
|
+
source_dataset_input_column: Optional[InputColumn],
|
|
87
|
+
unique_id_input_column: InputColumn,
|
|
88
|
+
) -> List[InputColumn]:
|
|
89
|
+
input_columns = combine_unique_id_input_columns(
|
|
90
|
+
source_dataset_input_column, unique_id_input_column
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
for br in blocking_rules:
|
|
94
|
+
parsed_columns = parse_columns_in_sql(
|
|
95
|
+
br.blocking_rule_sql, sqlglot_dialect=br.sqlglot_dialect
|
|
96
|
+
)
|
|
97
|
+
input_columns.extend(br._input_column(col.name) for col in parsed_columns)
|
|
98
|
+
|
|
99
|
+
return dedupe_preserving_order(input_columns)
|
|
100
|
+
|
|
101
|
+
|
|
79
102
|
class BlockingRule:
|
|
80
103
|
def __init__(
|
|
81
104
|
self,
|
|
@@ -98,6 +121,10 @@ class BlockingRule:
|
|
|
98
121
|
def sqlglot_dialect(self) -> str:
|
|
99
122
|
return SplinkDialect.from_string(self._sql_dialect_str).sqlglot_dialect
|
|
100
123
|
|
|
124
|
+
@property
|
|
125
|
+
def requires_blocking_input_materialisation(self) -> bool:
|
|
126
|
+
return False
|
|
127
|
+
|
|
101
128
|
@property
|
|
102
129
|
def sql_dialect(self) -> SplinkDialect:
|
|
103
130
|
return SplinkDialect.from_string(self._sql_dialect_str)
|
|
@@ -319,6 +346,8 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
319
346
|
unique_id_input_column: InputColumn,
|
|
320
347
|
br: BlockingRule,
|
|
321
348
|
link_type: "LinkTypeLiteralType",
|
|
349
|
+
input_tablename_l: str,
|
|
350
|
+
input_tablename_r: str,
|
|
322
351
|
left_chunk: tuple[int, int] | None = None,
|
|
323
352
|
right_chunk: tuple[int, int] | None = None,
|
|
324
353
|
) -> str:
|
|
@@ -349,11 +378,6 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
349
378
|
id_expr_l = _composite_unique_id_from_nodes_sql(unique_id_input_columns, "l")
|
|
350
379
|
id_expr_r = _composite_unique_id_from_nodes_sql(unique_id_input_columns, "r")
|
|
351
380
|
|
|
352
|
-
if link_type == "two_dataset_link_only":
|
|
353
|
-
where_condition = (
|
|
354
|
-
where_condition + " and l.source_dataset < r.source_dataset"
|
|
355
|
-
)
|
|
356
|
-
|
|
357
381
|
exclude_sql = self.exclude_pairs_generated_by_all_preceding_rules_sql(
|
|
358
382
|
source_dataset_input_column, unique_id_input_column
|
|
359
383
|
)
|
|
@@ -361,8 +385,8 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
361
385
|
select distinct
|
|
362
386
|
{id_expr_l} as {unique_id_col.name_l},
|
|
363
387
|
{id_expr_r} as {unique_id_col.name_r}
|
|
364
|
-
from
|
|
365
|
-
inner join
|
|
388
|
+
from {input_tablename_l} as l
|
|
389
|
+
inner join {input_tablename_r} as r
|
|
366
390
|
on ({br.blocking_rule_sql})
|
|
367
391
|
{where_condition}
|
|
368
392
|
{exclude_sql}
|
|
@@ -387,6 +411,10 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
387
411
|
|
|
388
412
|
return "false"
|
|
389
413
|
|
|
414
|
+
@property
|
|
415
|
+
def requires_blocking_input_materialisation(self) -> bool:
|
|
416
|
+
return True
|
|
417
|
+
|
|
390
418
|
def create_blocked_pairs_sql(
|
|
391
419
|
self,
|
|
392
420
|
*,
|
|
@@ -468,34 +496,65 @@ def materialise_exploded_id_tables(
|
|
|
468
496
|
return []
|
|
469
497
|
exploded_tables = []
|
|
470
498
|
|
|
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
499
|
for br in exploding_blocking_rules:
|
|
482
|
-
pipeline = CTEPipeline(
|
|
500
|
+
pipeline = CTEPipeline()
|
|
483
501
|
arrays_to_explode_cols = [
|
|
484
502
|
br._input_column(colname) for colname in br.array_columns_to_explode
|
|
485
503
|
]
|
|
504
|
+
input_columns = _columns_needed_for_blocking(
|
|
505
|
+
[*br.preceding_rules, br],
|
|
506
|
+
source_dataset_input_column=source_dataset_input_column,
|
|
507
|
+
unique_id_input_column=unique_id_input_column,
|
|
508
|
+
)
|
|
509
|
+
other_cols = [col for col in input_columns if col not in arrays_to_explode_cols]
|
|
486
510
|
|
|
487
|
-
|
|
511
|
+
if link_type == "two_dataset_link_only":
|
|
512
|
+
if source_dataset_input_column is None:
|
|
513
|
+
raise ValueError(
|
|
514
|
+
"source_dataset_input_column is required for two_dataset_link_only"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
left_sql, right_sql = select_two_dataset_link_only_input_tables_sqls(
|
|
518
|
+
splink_df_dict,
|
|
519
|
+
input_columns=input_columns,
|
|
520
|
+
source_dataset_input_column=source_dataset_input_column,
|
|
521
|
+
)
|
|
488
522
|
|
|
489
|
-
|
|
490
|
-
"
|
|
491
|
-
br.array_columns_to_explode,
|
|
492
|
-
[col.name for col in other_cols],
|
|
493
|
-
)
|
|
523
|
+
input_tablename_l = "__splink__df_concat_left_unnested"
|
|
524
|
+
input_tablename_r = "__splink__df_concat_right_unnested"
|
|
494
525
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
526
|
+
expl_sql_l = db_api.sql_dialect.explode_arrays_sql(
|
|
527
|
+
f"({left_sql})",
|
|
528
|
+
br.array_columns_to_explode,
|
|
529
|
+
[col.name for col in other_cols],
|
|
530
|
+
)
|
|
531
|
+
pipeline.enqueue_sql(expl_sql_l, input_tablename_l)
|
|
532
|
+
|
|
533
|
+
expl_sql_r = db_api.sql_dialect.explode_arrays_sql(
|
|
534
|
+
f"({right_sql})",
|
|
535
|
+
br.array_columns_to_explode,
|
|
536
|
+
[col.name for col in other_cols],
|
|
537
|
+
)
|
|
538
|
+
pipeline.enqueue_sql(expl_sql_r, input_tablename_r)
|
|
539
|
+
else:
|
|
540
|
+
sql = vertically_concatenate_sql(
|
|
541
|
+
splink_df_dict, source_dataset_input_column=source_dataset_input_column
|
|
542
|
+
)
|
|
543
|
+
pipeline.enqueue_sql(sql, "__splink__df_concat")
|
|
544
|
+
|
|
545
|
+
input_tablename_l = "__splink__df_concat_unnested"
|
|
546
|
+
input_tablename_r = "__splink__df_concat_unnested"
|
|
547
|
+
|
|
548
|
+
expl_sql = db_api.sql_dialect.explode_arrays_sql(
|
|
549
|
+
"__splink__df_concat",
|
|
550
|
+
br.array_columns_to_explode,
|
|
551
|
+
[col.name for col in other_cols],
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
pipeline.enqueue_sql(
|
|
555
|
+
expl_sql,
|
|
556
|
+
input_tablename_l,
|
|
557
|
+
)
|
|
499
558
|
|
|
500
559
|
base_name = "__splink__marginal_exploded_ids_blocking_rule"
|
|
501
560
|
table_name = f"{base_name}_mk_{br.match_key}"
|
|
@@ -505,6 +564,8 @@ def materialise_exploded_id_tables(
|
|
|
505
564
|
unique_id_input_column=unique_id_input_column,
|
|
506
565
|
br=br,
|
|
507
566
|
link_type=link_type,
|
|
567
|
+
input_tablename_l=input_tablename_l,
|
|
568
|
+
input_tablename_r=input_tablename_r,
|
|
508
569
|
left_chunk=left_chunk,
|
|
509
570
|
right_chunk=right_chunk,
|
|
510
571
|
)
|
|
@@ -559,11 +620,18 @@ def compute_blocked_pairs_from_concat_with_tf(
|
|
|
559
620
|
"source_dataset column"
|
|
560
621
|
)
|
|
561
622
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
source_dataset_input_column
|
|
623
|
+
input_columns = _columns_needed_for_blocking(
|
|
624
|
+
blocking_rules=blocking_rules,
|
|
625
|
+
source_dataset_input_column=source_dataset_input_column,
|
|
626
|
+
unique_id_input_column=unique_id_input_column,
|
|
627
|
+
)
|
|
628
|
+
left_sql, right_sql = select_two_dataset_link_only_input_tables_sqls(
|
|
629
|
+
splink_df_dict,
|
|
630
|
+
input_columns=input_columns,
|
|
631
|
+
source_dataset_input_column=source_dataset_input_column,
|
|
565
632
|
)
|
|
566
|
-
pipeline.
|
|
633
|
+
pipeline.enqueue_sql(left_sql, f"{df_concat_with_tf_table_name}_left")
|
|
634
|
+
pipeline.enqueue_sql(right_sql, f"{df_concat_with_tf_table_name}_right")
|
|
567
635
|
|
|
568
636
|
blocking_input_tablename_l = f"{df_concat_with_tf_table_name}_left"
|
|
569
637
|
blocking_input_tablename_r = f"{df_concat_with_tf_table_name}_right"
|
|
@@ -704,7 +772,7 @@ def block_using_rules_sqls(
|
|
|
704
772
|
)
|
|
705
773
|
br_sqls.append(sql)
|
|
706
774
|
|
|
707
|
-
sql =
|
|
775
|
+
sql = join_sql_with_union_all(br_sqls)
|
|
708
776
|
|
|
709
777
|
if any(isinstance(br, ExplodingBlockingRule) for br in blocking_rules):
|
|
710
778
|
sqls.append(
|