splink 5.0.0.dev3__tar.gz → 5.0.0.dev5__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.dev3 → splink-5.0.0.dev5}/.gitignore +6 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/PKG-INFO +10 -9
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/README.md +3 -3
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/pyproject.toml +14 -19
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/__init__.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/backends/duckdb.py +2 -1
- splink-5.0.0.dev5/splink/blocking_analysis.py +11 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/accuracy.py +3 -3
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/blocking.py +45 -8
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/blocking_analysis.py +256 -263
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/blocking_rule_creator_utils.py +4 -3
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/blocking_rule_library.py +4 -3
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/cache_dict_with_logging.py +5 -5
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/charts.py +77 -78
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/cluster_studio.py +32 -33
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/clustering.py +2 -2
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/column_expression.py +9 -7
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison.py +4 -4
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_creator.py +8 -6
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_level.py +30 -27
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_level_composition.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_level_library.py +27 -12
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_library.py +13 -33
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/completeness.py +6 -6
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/connected_components.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/database_api.py +65 -23
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/databricks/enable_splink.py +2 -2
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/datasets/splink_datasets.py +11 -10
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/datasets/utils.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/dialects.py +108 -88
- splink-5.0.0.dev5/splink/internals/duckdb/database_api.py +178 -0
- splink-5.0.0.dev5/splink/internals/duckdb/database_api_with_profiling.py +157 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/duckdb/dataframe.py +4 -4
- splink-5.0.0.dev5/splink/internals/duckdb/parquet_materialisation.py +102 -0
- splink-5.0.0.dev5/splink/internals/duckdb/parquet_write_options.py +36 -0
- splink-5.0.0.dev5/splink/internals/duckdb/pruned_prediction.py +140 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/edge_metrics.py +16 -5
- splink-5.0.0.dev5/splink/internals/em_sampling.py +236 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/em_training_session.py +59 -7
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/estimate_u.py +13 -18
- splink-5.0.0.dev5/splink/internals/files/chart_defs/.gitignore +1 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +35 -28
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weights_interactive_history.json +9 -3
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/precision_recall.json +4 -2
- splink-5.0.0.dev5/splink/internals/files/chart_defs/profile_data_outer.json +5 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/roc.json +4 -2
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/labelling_tool/slt.js +2 -2
- splink-5.0.0.dev3/splink/internals/files/labelling_tool/template.j2 → splink-5.0.0.dev5/splink/internals/files/labelling_tool/template.html +32 -40
- splink-5.0.0.dev5/splink/internals/files/spark_jars/scala-udf-similarity-0.2.1_spark4.x.jar +0 -0
- splink-5.0.0.dev3/splink/internals/files/splink_cluster_studio/cluster_template.j2 → splink-5.0.0.dev5/splink/internals/files/splink_cluster_studio/cluster_template.html +36 -40
- splink-5.0.0.dev3/splink/internals/files/splink_comparison_viewer/template.j2 → splink-5.0.0.dev5/splink/internals/files/splink_comparison_viewer/template.html +29 -28
- splink-5.0.0.dev5/splink/internals/files/templates/chart_fragment.html +67 -0
- splink-5.0.0.dev5/splink/internals/files/templates/single_chart_template.html +15 -0
- splink-5.0.0.dev5/splink/internals/html_utils.py +86 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/input_column.py +22 -11
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/labelling_tool.py +72 -19
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/linker.py +23 -190
- splink-5.0.0.dev5/splink/internals/linker_components/blocking_analysis.py +158 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/linker_components/clustering.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/linker_components/evaluation.py +4 -4
- splink-5.0.0.dev5/splink/internals/linker_components/inference.py +1571 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/linker_components/misc.py +3 -25
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/linker_components/table_management.py +65 -87
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/linker_components/training.py +36 -9
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/linker_components/visualisations.py +20 -14
- splink-5.0.0.dev5/splink/internals/logging_messages.py +6 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/lower_id_on_lhs.py +6 -11
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/match_weights_histogram.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/misc.py +51 -18
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/one_to_one_clustering.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/parse_sql.py +4 -4
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/pipeline.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/postgres/database_api.py +4 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/postgres/dataframe.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/predict.py +4 -4
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/profile_data.py +106 -73
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/settings.py +2 -2
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/settings_validation/settings_column_cleaner.py +3 -3
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/settings_validation/settings_validation_log_strings.py +4 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/spark/database_api.py +5 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/spark/dataframe.py +7 -7
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/spark/jar_location.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/splink_comparison_viewer.py +80 -10
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/splink_dataframe.py +39 -11
- splink-5.0.0.dev5/splink/internals/splink_logging.py +56 -0
- splink-5.0.0.dev5/splink/internals/splinkdataframe_utils.py +99 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/sql_transform.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/sqlite/dataframe.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/term_frequencies.py +4 -5
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/testing.py +2 -2
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/unlinkables.py +1 -1
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/vertically_concatenate.py +4 -4
- splink-5.0.0.dev5/splink/logging.py +9 -0
- splink-5.0.0.dev3/splink/blocking_analysis.py +0 -13
- splink-5.0.0.dev3/splink/internals/cost_of_blocking_rules.py +0 -116
- splink-5.0.0.dev3/splink/internals/duckdb/database_api.py +0 -93
- splink-5.0.0.dev3/splink/internals/duckdb/database_api_with_profiling.py +0 -71
- splink-5.0.0.dev3/splink/internals/files/chart_defs/comparator_score_chart.json +0 -169
- splink-5.0.0.dev3/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -196
- splink-5.0.0.dev3/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -94
- splink-5.0.0.dev3/splink/internals/files/settings_jsonschema.json +0 -317
- splink-5.0.0.dev3/splink/internals/files/templates/single_chart_template.html +0 -31
- splink-5.0.0.dev3/splink/internals/find_brs_with_comparison_counts_below_threshold.py +0 -278
- splink-5.0.0.dev3/splink/internals/linker_components/inference.py +0 -971
- splink-5.0.0.dev3/splink/internals/logging_messages.py +0 -10
- splink-5.0.0.dev3/splink/internals/optimise_cost_of_brs.py +0 -219
- splink-5.0.0.dev3/splink/internals/similarity_analysis.py +0 -318
- splink-5.0.0.dev3/splink/internals/splinkdataframe_utils.py +0 -53
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/LICENSE +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/backends/postgres.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/backends/spark.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/backends/sqlite.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/clustering.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/comparison_level_library.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/comparison_library.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/datasets.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/exploratory.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/block_from_labels.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/blocking_rule_creator.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/chunking.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_level_creator.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_level_sql.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_vector_distribution.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/comparison_vector_values.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/constants.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/databricks/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/datasets/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/datasets/metadata.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/duckdb/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/duckdb/duckdb_helpers/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/exceptions.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/expectation_maximisation.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/completeness.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/profile_data.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/external_js/d3@7.8.5 +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega@5.31.0 +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/spark_jars/scala-udf-similarity-0.1.2_spark3.x.jar +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/spark_jars/scala-udf-similarity-0.2.0_spark4.x.jar +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/find_matches_to_new_records.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/graph_metrics.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/linker_components/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/m_from_labels.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/m_training.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/m_u_records_to_parameters.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/postgres/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/realtime.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/settings_creator.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/settings_validation/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/settings_validation/valid_types.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/spark/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/spark/database_api_with_profiling.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/version.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/sqlite/__init__.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/sqlite/database_api.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/unique_id_concat.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/internals/waterfall_chart.py +0 -0
- {splink-5.0.0.dev3 → splink-5.0.0.dev5}/splink/py.typed +0 -0
|
@@ -169,7 +169,12 @@ cython_debug/
|
|
|
169
169
|
# html files opt-in only
|
|
170
170
|
*.html
|
|
171
171
|
!splink/**/templates/**/*.html
|
|
172
|
-
!docs/**/*.html
|
|
172
|
+
!docs/demos/examples/**/*.html
|
|
173
|
+
!docs/demos/tutorials/**/*.html
|
|
174
|
+
!docs/overrides/**/*.html
|
|
175
|
+
|
|
176
|
+
# don't store ipynb here
|
|
177
|
+
*.ipynb
|
|
173
178
|
|
|
174
179
|
.DS_Store
|
|
175
180
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: splink
|
|
3
|
-
Version: 5.0.0.
|
|
3
|
+
Version: 5.0.0.dev5
|
|
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
|
|
@@ -8,12 +8,13 @@ Author: Sam Lindsay, Theodore Manassis, Tom Hepworth, Andy Bond, Ross Kennedy
|
|
|
8
8
|
Author-email: Robin Linacre <robinlinacre@hotmail.com>
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Python: <4.0.0,>=3.
|
|
12
|
-
Requires-Dist: altair>=5.0.1
|
|
11
|
+
Requires-Python: <4.0.0,>=3.10.0
|
|
13
12
|
Requires-Dist: duckdb>=0.9.2
|
|
14
|
-
Requires-Dist: igraph>=0.11.2
|
|
15
|
-
Requires-Dist: jinja2>=3.0.3
|
|
16
13
|
Requires-Dist: sqlglot>=17.6.0
|
|
14
|
+
Provides-Extra: altair
|
|
15
|
+
Requires-Dist: altair>=5.0.1; extra == 'altair'
|
|
16
|
+
Provides-Extra: igraph
|
|
17
|
+
Requires-Dist: igraph>=0.11.2; extra == 'igraph'
|
|
17
18
|
Provides-Extra: postgres
|
|
18
19
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'postgres'
|
|
19
20
|
Requires-Dist: sqlalchemy>=2.0.0; extra == 'postgres'
|
|
@@ -60,15 +61,15 @@ Splink's linkage algorithm is based on Fellegi-Sunter's model of record linkage,
|
|
|
60
61
|
|
|
61
62
|
Consider the following records that lack a unique person identifier:
|
|
62
63
|
|
|
63
|
-

|
|
64
65
|
|
|
65
66
|
Splink predicts which rows link together:
|
|
66
67
|
|
|
67
|
-

|
|
68
69
|
|
|
69
70
|
and clusters these links to produce an estimated person ID:
|
|
70
71
|
|
|
71
|
-

|
|
72
73
|
|
|
73
74
|
## What data does Splink work best with?
|
|
74
75
|
|
|
@@ -31,15 +31,15 @@ Splink's linkage algorithm is based on Fellegi-Sunter's model of record linkage,
|
|
|
31
31
|
|
|
32
32
|
Consider the following records that lack a unique person identifier:
|
|
33
33
|
|
|
34
|
-

|
|
35
35
|
|
|
36
36
|
Splink predicts which rows link together:
|
|
37
37
|
|
|
38
|
-

|
|
39
39
|
|
|
40
40
|
and clusters these links to produce an estimated person ID:
|
|
41
41
|
|
|
42
|
-

|
|
43
43
|
|
|
44
44
|
## What data does Splink work best with?
|
|
45
45
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "splink"
|
|
3
|
-
version = "5.0.0.
|
|
3
|
+
version = "5.0.0.dev5"
|
|
4
4
|
description = "Fast probabilistic data linkage at scale"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Robin Linacre", email = "robinlinacre@hotmail.com" },
|
|
@@ -10,18 +10,17 @@ authors = [
|
|
|
10
10
|
{ name = "Andy Bond" },
|
|
11
11
|
{ name = "Ross Kennedy" },
|
|
12
12
|
]
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.10.0,<4.0.0"
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
license = "MIT"
|
|
16
16
|
dependencies = [
|
|
17
17
|
"duckdb>=0.9.2",
|
|
18
18
|
"sqlglot>=17.6.0",
|
|
19
|
-
"altair>=5.0.1",
|
|
20
|
-
"Jinja2>=3.0.3",
|
|
21
|
-
"igraph>=0.11.2",
|
|
22
19
|
]
|
|
23
20
|
|
|
24
21
|
[project.optional-dependencies]
|
|
22
|
+
altair = ["altair>=5.0.1"]
|
|
23
|
+
igraph = ["igraph>=0.11.2"]
|
|
25
24
|
pyspark = ["pyspark>=3.5.0"]
|
|
26
25
|
spark = ["pyspark>=3.5.0"]
|
|
27
26
|
# sqlite doesn't _require_ this functionality, but it's used if we want to use fuzzy levels
|
|
@@ -38,13 +37,6 @@ Repository = "https://github.com/moj-analytical-services/splink"
|
|
|
38
37
|
# allows us to easily check package functionality with just these installed
|
|
39
38
|
# (and not the rest of our required dependencies)
|
|
40
39
|
core = ["sqlglot", "duckdb"]
|
|
41
|
-
demos = [
|
|
42
|
-
"ipywidgets>=8.1.7",
|
|
43
|
-
"jupytext>=1.18.1",
|
|
44
|
-
"nbmake>=1.5.5",
|
|
45
|
-
"pseudopeople>=1.2.6",
|
|
46
|
-
"pytest-xdist>=3.6.1",
|
|
47
|
-
]
|
|
48
40
|
# dev deliberately doesn't include spark, so we can opt in to different spark versions
|
|
49
41
|
dev = [
|
|
50
42
|
{ "include-group" = "linting" },
|
|
@@ -56,10 +48,12 @@ spark-3 = [
|
|
|
56
48
|
]
|
|
57
49
|
spark-4 = ["pyspark>=4.0.0"]
|
|
58
50
|
docs = [
|
|
51
|
+
"altair>=5.0.1",
|
|
59
52
|
"griffe>=1.4.0",
|
|
60
53
|
# keep jinja2 relaxed so it doesn't constrain us in rest of dev
|
|
61
54
|
"Jinja2>=3.1.6",
|
|
62
55
|
"mkdocs>=1.6.1",
|
|
56
|
+
"mkdocs-llmstxt>=0.5.0",
|
|
63
57
|
# TODO: maybe we can use this to remove generating dataset docs as separate step
|
|
64
58
|
#"mkdocs-gen-files==0.5.0",
|
|
65
59
|
"mkdocs-material>=9.6.22",
|
|
@@ -72,7 +66,7 @@ docs = [
|
|
|
72
66
|
"neoteroi-mkdocs>=1.1.2",
|
|
73
67
|
"pymdown-extensions>=10.15",
|
|
74
68
|
]
|
|
75
|
-
linting = ["ruff>=0.
|
|
69
|
+
linting = ["ruff>=0.15.18"]
|
|
76
70
|
pandas = ["pandas>=1.3.5", "numpy>=1.19.3"]
|
|
77
71
|
postgres = [
|
|
78
72
|
"psycopg2-binary>=2.9.0",
|
|
@@ -82,26 +76,27 @@ sqlite = [
|
|
|
82
76
|
"rapidfuzz>=3.10.0",
|
|
83
77
|
]
|
|
84
78
|
testing-core = [
|
|
79
|
+
"igraph>=0.11.2",
|
|
85
80
|
"pytest>=7.3",
|
|
86
81
|
"pyarrow>=15.0.0",
|
|
87
82
|
"networkx>=2.5.1",
|
|
88
83
|
"pytest-cov>=5.0.0",
|
|
84
|
+
"pytest-xdist>=3.0.0",
|
|
89
85
|
{ "include-group" = "postgres" },
|
|
90
86
|
{ "include-group" = "sqlite" },
|
|
91
|
-
# TODO: temporary measure until we can depanda tests
|
|
92
|
-
{ "include-group" = "pandas" },
|
|
93
87
|
]
|
|
94
88
|
testing = [
|
|
95
89
|
{ "include-group" = "testing-core" },
|
|
96
90
|
{ "include-group" = "spark-4" },
|
|
97
91
|
]
|
|
98
|
-
typechecking = ["mypy
|
|
92
|
+
typechecking = ["mypy>=2.1.0"]
|
|
99
93
|
# we will migrate to ty, but won't happen all at once, so run in parallel
|
|
100
94
|
typechecking-dev = [
|
|
101
95
|
"ty>=0.0.21",
|
|
102
96
|
]
|
|
103
97
|
|
|
104
98
|
[tool.uv]
|
|
99
|
+
exclude-newer = "7 days"
|
|
105
100
|
default-groups = [
|
|
106
101
|
"dev",
|
|
107
102
|
"spark-4"
|
|
@@ -179,9 +174,6 @@ markers = [
|
|
|
179
174
|
[tool.mypy]
|
|
180
175
|
python_version = "3.10"
|
|
181
176
|
packages = "splink"
|
|
182
|
-
# for now at least allow implicit optionals
|
|
183
|
-
# to cut down on noise. Easy to fix.
|
|
184
|
-
implicit_optional = true
|
|
185
177
|
# for now, ignore missing imports
|
|
186
178
|
# can remove later and install stubs, where existent
|
|
187
179
|
ignore_missing_imports = true
|
|
@@ -203,3 +195,6 @@ disallow_any_generics = true
|
|
|
203
195
|
# disallow_untyped_calls = true
|
|
204
196
|
disallow_incomplete_defs = true
|
|
205
197
|
# disallow_untyped_defs = true
|
|
198
|
+
|
|
199
|
+
[tool.ty.rules]
|
|
200
|
+
unresolved-import = "ignore"
|
|
@@ -2,5 +2,6 @@ from splink.internals.duckdb.database_api import DuckDBAPI
|
|
|
2
2
|
from splink.internals.duckdb.database_api_with_profiling import (
|
|
3
3
|
DuckDBAPIWithProfiling,
|
|
4
4
|
)
|
|
5
|
+
from splink.internals.duckdb.parquet_write_options import ParquetWriteOptions
|
|
5
6
|
|
|
6
|
-
__all__ = ["DuckDBAPI", "DuckDBAPIWithProfiling"]
|
|
7
|
+
__all__ = ["DuckDBAPI", "DuckDBAPIWithProfiling", "ParquetWriteOptions"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .internals.blocking_analysis import (
|
|
2
|
+
chart_comparisons_from_blocking_rules,
|
|
3
|
+
count_comparisons_from_blocking_rules,
|
|
4
|
+
n_largest_blocks,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"count_comparisons_from_blocking_rules",
|
|
9
|
+
"chart_comparisons_from_blocking_rules",
|
|
10
|
+
"n_largest_blocks",
|
|
11
|
+
]
|
|
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
|
|
|
26
26
|
def truth_space_table_from_labels_with_predictions_sqls(
|
|
27
27
|
threshold_actual: float = 0.5,
|
|
28
28
|
match_weight_round_to_nearest: float | None = None,
|
|
29
|
-
total_labels: int = None,
|
|
29
|
+
total_labels: int | None = None,
|
|
30
30
|
positives_not_captured_by_blocking_rules_scored_as_zero: bool = True,
|
|
31
31
|
) -> list[dict[str, str]]:
|
|
32
32
|
"""
|
|
@@ -336,7 +336,7 @@ def truth_space_table_from_labels_column(
|
|
|
336
336
|
linker: "Linker",
|
|
337
337
|
label_colname: str,
|
|
338
338
|
threshold_actual: float = 0.5,
|
|
339
|
-
match_weight_round_to_nearest: float = None,
|
|
339
|
+
match_weight_round_to_nearest: float | None = None,
|
|
340
340
|
positives_not_captured_by_blocking_rules_scored_as_zero: bool = True,
|
|
341
341
|
) -> SplinkDataFrame:
|
|
342
342
|
# First we need to calculate the number of implicit true negatives
|
|
@@ -359,7 +359,7 @@ def truth_space_table_from_labels_column(
|
|
|
359
359
|
|
|
360
360
|
pipeline.enqueue_sql(sql, "__splink__cartesian_product")
|
|
361
361
|
cartesian_count = linker._db_api.sql_pipeline_to_splink_dataframe(pipeline)
|
|
362
|
-
row_count_df = cartesian_count.
|
|
362
|
+
row_count_df = cartesian_count.as_record_list()
|
|
363
363
|
cartesian_count.drop_table_from_database_and_remove_from_cache()
|
|
364
364
|
|
|
365
365
|
total_labels = calculate_cartesian(row_count_df, link_type)
|
|
@@ -5,14 +5,17 @@ import time
|
|
|
5
5
|
from typing import TYPE_CHECKING, Any, List, Literal, Optional, TypedDict
|
|
6
6
|
|
|
7
7
|
from sqlglot import parse_one
|
|
8
|
-
from sqlglot.expressions import Column,
|
|
8
|
+
from sqlglot.expressions import Column, Identifier, Join
|
|
9
9
|
from sqlglot.optimizer.eliminate_joins import join_condition
|
|
10
10
|
from sqlglot.optimizer.optimizer import optimize
|
|
11
11
|
from sqlglot.optimizer.simplify import flatten
|
|
12
12
|
|
|
13
|
-
from splink.internals.chunking import
|
|
13
|
+
from splink.internals.chunking import (
|
|
14
|
+
_chunk_assignment_sql,
|
|
15
|
+
)
|
|
14
16
|
from splink.internals.database_api import DatabaseAPISubClass
|
|
15
17
|
from splink.internals.dialects import SplinkDialect
|
|
18
|
+
from splink.internals.em_sampling import _em_sample_filter_sql
|
|
16
19
|
from splink.internals.input_column import InputColumn
|
|
17
20
|
from splink.internals.misc import (
|
|
18
21
|
dedupe_preserving_order,
|
|
@@ -32,6 +35,9 @@ logger = logging.getLogger(__name__)
|
|
|
32
35
|
|
|
33
36
|
# https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports
|
|
34
37
|
if TYPE_CHECKING:
|
|
38
|
+
# Expr only exists in sqlglot >= 30.0.0
|
|
39
|
+
from sqlglot import Expr
|
|
40
|
+
|
|
35
41
|
from splink.internals.settings import LinkTypeLiteralType
|
|
36
42
|
|
|
37
43
|
user_input_link_type_options = Literal["link_only", "link_and_dedupe", "dedupe_only"]
|
|
@@ -211,10 +217,11 @@ class BlockingRule:
|
|
|
211
217
|
on
|
|
212
218
|
({self.blocking_rule_sql})
|
|
213
219
|
{where_condition}
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
unique_id_input_column
|
|
217
|
-
|
|
220
|
+
{
|
|
221
|
+
self.exclude_pairs_generated_by_all_preceding_rules_sql(
|
|
222
|
+
source_dataset_input_column, unique_id_input_column
|
|
223
|
+
)
|
|
224
|
+
}
|
|
218
225
|
"""
|
|
219
226
|
return sql
|
|
220
227
|
|
|
@@ -247,7 +254,7 @@ class BlockingRule:
|
|
|
247
254
|
list of tuples like [(name, name), (substr(name,1,2), substr(name,2,3))]
|
|
248
255
|
"""
|
|
249
256
|
|
|
250
|
-
def remove_table_prefix(tree:
|
|
257
|
+
def remove_table_prefix(tree: Expr) -> Expr:
|
|
251
258
|
for c in tree.find_all(Column):
|
|
252
259
|
del c.args["table"]
|
|
253
260
|
return tree
|
|
@@ -260,7 +267,7 @@ class BlockingRule:
|
|
|
260
267
|
|
|
261
268
|
rmtp = remove_table_prefix
|
|
262
269
|
|
|
263
|
-
keys_de_prefixed: list[tuple[
|
|
270
|
+
keys_de_prefixed: list[tuple[Expr, Expr]] = [
|
|
264
271
|
(rmtp(i), rmtp(j)) for (i, j) in keys_zipped
|
|
265
272
|
]
|
|
266
273
|
|
|
@@ -350,6 +357,8 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
350
357
|
input_tablename_r: str,
|
|
351
358
|
left_chunk: tuple[int, int] | None = None,
|
|
352
359
|
right_chunk: tuple[int, int] | None = None,
|
|
360
|
+
sample_threshold: int | None = None,
|
|
361
|
+
sample_modulus: int | None = None,
|
|
353
362
|
) -> str:
|
|
354
363
|
"""generates a table of the marginal id pairs from the exploded blocking rule
|
|
355
364
|
i.e. pairs are only created that match this blocking rule and NOT any of
|
|
@@ -360,6 +369,9 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
360
369
|
left side records.
|
|
361
370
|
right_chunk: Optional tuple of (chunk_number, total_chunks) for filtering
|
|
362
371
|
right side records.
|
|
372
|
+
sample_threshold: Optional integer threshold for deterministic hash
|
|
373
|
+
sampling of both sides of the join (used by blocking analysis).
|
|
374
|
+
sample_modulus: Optional modulus for the deterministic hash sample.
|
|
363
375
|
"""
|
|
364
376
|
|
|
365
377
|
unique_id_col = unique_id_input_column
|
|
@@ -373,6 +385,8 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
373
385
|
left_chunk=left_chunk,
|
|
374
386
|
right_chunk=right_chunk,
|
|
375
387
|
sql_dialect=self.sql_dialect,
|
|
388
|
+
sample_threshold=sample_threshold,
|
|
389
|
+
sample_modulus=sample_modulus,
|
|
376
390
|
)
|
|
377
391
|
|
|
378
392
|
id_expr_l = _composite_unique_id_from_nodes_sql(unique_id_input_columns, "l")
|
|
@@ -479,6 +493,8 @@ def materialise_exploded_id_tables(
|
|
|
479
493
|
unique_id_input_column: InputColumn,
|
|
480
494
|
left_chunk: tuple[int, int] | None = None,
|
|
481
495
|
right_chunk: tuple[int, int] | None = None,
|
|
496
|
+
sample_threshold: int | None = None,
|
|
497
|
+
sample_modulus: int | None = None,
|
|
482
498
|
) -> list[ExplodingBlockingRule]:
|
|
483
499
|
"""Materialise exploded ID pair tables for exploding blocking rules.
|
|
484
500
|
|
|
@@ -487,6 +503,9 @@ def materialise_exploded_id_tables(
|
|
|
487
503
|
left side records.
|
|
488
504
|
right_chunk: Optional tuple of (chunk_number, total_chunks) for filtering
|
|
489
505
|
right side records.
|
|
506
|
+
sample_threshold: Optional integer threshold for deterministic hash
|
|
507
|
+
sampling of both sides of the join (used by blocking analysis).
|
|
508
|
+
sample_modulus: Optional modulus for the deterministic hash sample.
|
|
490
509
|
"""
|
|
491
510
|
exploding_blocking_rules = [
|
|
492
511
|
br for br in blocking_rules if isinstance(br, ExplodingBlockingRule)
|
|
@@ -568,6 +587,8 @@ def materialise_exploded_id_tables(
|
|
|
568
587
|
input_tablename_r=input_tablename_r,
|
|
569
588
|
left_chunk=left_chunk,
|
|
570
589
|
right_chunk=right_chunk,
|
|
590
|
+
sample_threshold=sample_threshold,
|
|
591
|
+
sample_modulus=sample_modulus,
|
|
571
592
|
)
|
|
572
593
|
|
|
573
594
|
pipeline.enqueue_sql(sql, table_name)
|
|
@@ -680,6 +701,8 @@ def _sql_gen_where_condition(
|
|
|
680
701
|
left_chunk: tuple[int, int] | None = None,
|
|
681
702
|
right_chunk: tuple[int, int] | None = None,
|
|
682
703
|
sql_dialect: "SplinkDialect | None" = None,
|
|
704
|
+
sample_threshold: int | None = None,
|
|
705
|
+
sample_modulus: int | None = None,
|
|
683
706
|
) -> str:
|
|
684
707
|
id_expr_l = _composite_unique_id_from_nodes_sql(unique_id_cols, "l")
|
|
685
708
|
id_expr_r = _composite_unique_id_from_nodes_sql(unique_id_cols, "r")
|
|
@@ -708,6 +731,16 @@ def _sql_gen_where_condition(
|
|
|
708
731
|
unique_id_cols, chunk_num, total_chunks, "r", sql_dialect
|
|
709
732
|
)
|
|
710
733
|
|
|
734
|
+
if sample_threshold is not None and sample_modulus is not None:
|
|
735
|
+
if sql_dialect is None:
|
|
736
|
+
raise ValueError("EM sampling requires a SQL dialect")
|
|
737
|
+
where_condition += _em_sample_filter_sql(
|
|
738
|
+
unique_id_cols, sample_threshold, sample_modulus, "l", sql_dialect
|
|
739
|
+
)
|
|
740
|
+
where_condition += _em_sample_filter_sql(
|
|
741
|
+
unique_id_cols, sample_threshold, sample_modulus, "r", sql_dialect
|
|
742
|
+
)
|
|
743
|
+
|
|
711
744
|
return where_condition
|
|
712
745
|
|
|
713
746
|
|
|
@@ -721,6 +754,8 @@ def block_using_rules_sqls(
|
|
|
721
754
|
unique_id_input_column: InputColumn,
|
|
722
755
|
left_chunk: tuple[int, int] | None = None,
|
|
723
756
|
right_chunk: tuple[int, int] | None = None,
|
|
757
|
+
sample_threshold: int | None = None,
|
|
758
|
+
sample_modulus: int | None = None,
|
|
724
759
|
) -> list[dict[str, str]]:
|
|
725
760
|
"""Use the blocking rules specified in the linker's settings object to
|
|
726
761
|
generate a SQL statement that will create pairwise record comparions
|
|
@@ -751,6 +786,8 @@ def block_using_rules_sqls(
|
|
|
751
786
|
left_chunk=left_chunk,
|
|
752
787
|
right_chunk=right_chunk,
|
|
753
788
|
sql_dialect=sql_dialect,
|
|
789
|
+
sample_threshold=sample_threshold,
|
|
790
|
+
sample_modulus=sample_modulus,
|
|
754
791
|
)
|
|
755
792
|
|
|
756
793
|
# Cover the case where there are no blocking rules
|