splink 5.0.0.dev2__tar.gz → 5.0.0.dev4__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.dev2 → splink-5.0.0.dev4}/PKG-INFO +5 -5
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/README.md +3 -3
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/pyproject.toml +8 -6
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/__init__.py +1 -1
- splink-5.0.0.dev4/splink/backends/duckdb.py +6 -0
- splink-5.0.0.dev4/splink/backends/spark.py +5 -0
- splink-5.0.0.dev4/splink/blocking_analysis.py +11 -0
- splink-5.0.0.dev4/splink/exploratory.py +4 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/accuracy.py +7 -12
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/blocking.py +59 -10
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/blocking_analysis.py +296 -286
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/cache_dict_with_logging.py +2 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/charts.py +31 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/cluster_studio.py +19 -15
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/clustering.py +4 -3
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/column_expression.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison.py +7 -6
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_level.py +20 -18
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_level_library.py +2 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_vector_values.py +17 -14
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/completeness.py +4 -3
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/connected_components.py +3 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/database_api.py +64 -31
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/datasets/splink_datasets.py +11 -10
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/dialects.py +65 -76
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/duckdb/database_api.py +5 -0
- splink-5.0.0.dev4/splink/internals/duckdb/database_api_with_profiling.py +71 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/duckdb/dataframe.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/edge_metrics.py +16 -5
- splink-5.0.0.dev4/splink/internals/em_sampling.py +236 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/em_training_session.py +64 -9
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/estimate_u.py +82 -47
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/expectation_maximisation.py +5 -4
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +35 -28
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/match_weights_interactive_history.json +9 -3
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/precision_recall.json +4 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/roc.json +4 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/labelling_tool/slt.js +2 -2
- splink-5.0.0.dev4/splink/internals/files/spark_jars/scala-udf-similarity-0.2.1_spark4.x.jar +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/labelling_tool.py +28 -13
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/linker.py +31 -198
- splink-5.0.0.dev4/splink/internals/linker_components/blocking_analysis.py +164 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/linker_components/evaluation.py +3 -3
- splink-5.0.0.dev4/splink/internals/linker_components/inference.py +1550 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/linker_components/misc.py +3 -25
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/linker_components/table_management.py +65 -128
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/linker_components/training.py +41 -21
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/linker_components/visualisations.py +7 -7
- splink-5.0.0.dev4/splink/internals/logging_messages.py +6 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/lower_id_on_lhs.py +6 -11
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/m_from_labels.py +2 -3
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/m_training.py +8 -9
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/match_weights_histogram.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/misc.py +44 -16
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/one_to_one_clustering.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/parse_sql.py +4 -4
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/pipeline.py +8 -5
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/postgres/dataframe.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/predict.py +34 -19
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/profile_data.py +8 -8
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/settings.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/settings_validation/settings_column_cleaner.py +2 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/settings_validation/settings_validation_log_strings.py +4 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/similarity_analysis.py +10 -10
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/spark/database_api.py +5 -0
- splink-5.0.0.dev4/splink/internals/spark/database_api_with_profiling.py +174 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/spark/dataframe.py +5 -5
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/spark/jar_location.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/splink_dataframe.py +41 -12
- splink-5.0.0.dev4/splink/internals/splink_logging.py +56 -0
- splink-5.0.0.dev4/splink/internals/splinkdataframe_utils.py +99 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/sqlite/dataframe.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/term_frequencies.py +56 -116
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/testing.py +2 -2
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/unlinkables.py +1 -1
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/vertically_concatenate.py +27 -106
- splink-5.0.0.dev4/splink/logging.py +9 -0
- splink-5.0.0.dev2/splink/backends/duckdb.py +0 -3
- splink-5.0.0.dev2/splink/backends/spark.py +0 -4
- splink-5.0.0.dev2/splink/blocking_analysis.py +0 -13
- splink-5.0.0.dev2/splink/exploratory.py +0 -5
- splink-5.0.0.dev2/splink/internals/cost_of_blocking_rules.py +0 -116
- splink-5.0.0.dev2/splink/internals/default_from_jsonschema.py +0 -20
- splink-5.0.0.dev2/splink/internals/files/settings_jsonschema.json +0 -317
- splink-5.0.0.dev2/splink/internals/find_brs_with_comparison_counts_below_threshold.py +0 -278
- splink-5.0.0.dev2/splink/internals/linker_components/inference.py +0 -989
- splink-5.0.0.dev2/splink/internals/logging_messages.py +0 -10
- splink-5.0.0.dev2/splink/internals/optimise_cost_of_brs.py +0 -219
- splink-5.0.0.dev2/splink/internals/splinkdataframe_utils.py +0 -53
- splink-5.0.0.dev2/splink/internals/validate_jsonschema.py +0 -44
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/.gitignore +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/LICENSE +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/backends/postgres.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/backends/sqlite.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/clustering.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/comparison_level_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/comparison_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/datasets.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/block_from_labels.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/blocking_rule_creator.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/blocking_rule_creator_utils.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/chunking.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_creator.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_level_composition.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_level_creator.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_level_sql.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_library.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/comparison_vector_distribution.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/constants.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/databricks/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/databricks/enable_splink.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/datasets/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/datasets/metadata.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/datasets/utils.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/duckdb/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/duckdb/duckdb_helpers/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/exceptions.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/comparator_score_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/completeness.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/profile_data.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/external_js/d3@7.8.5 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/external_js/vega@5.31.0 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/labelling_tool/template.j2 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/spark_jars/scala-udf-similarity-0.1.2_spark3.x.jar +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/spark_jars/scala-udf-similarity-0.2.0_spark4.x.jar +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/splink_cluster_studio/cluster_template.j2 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/splink_comparison_viewer/template.j2 +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/files/templates/single_chart_template.html +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/find_matches_to_new_records.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/graph_metrics.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/input_column.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/linker_components/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/linker_components/clustering.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/m_u_records_to_parameters.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/postgres/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/postgres/database_api.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/realtime.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/settings_creator.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/settings_validation/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/settings_validation/valid_types.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/spark/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/spark/spark_helpers/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/spark/spark_helpers/version.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/splink_comparison_viewer.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/sql_transform.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/sqlite/__init__.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/sqlite/database_api.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/unique_id_concat.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/internals/waterfall_chart.py +0 -0
- {splink-5.0.0.dev2 → splink-5.0.0.dev4}/splink/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splink
|
|
3
|
-
Version: 5.0.0.
|
|
3
|
+
Version: 5.0.0.dev4
|
|
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,7 +8,7 @@ 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.
|
|
11
|
+
Requires-Python: <4.0.0,>=3.10.0
|
|
12
12
|
Requires-Dist: altair>=5.0.1
|
|
13
13
|
Requires-Dist: duckdb>=0.9.2
|
|
14
14
|
Requires-Dist: igraph>=0.11.2
|
|
@@ -60,15 +60,15 @@ Splink's linkage algorithm is based on Fellegi-Sunter's model of record linkage,
|
|
|
60
60
|
|
|
61
61
|
Consider the following records that lack a unique person identifier:
|
|
62
62
|
|
|
63
|
-

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

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

|
|
72
72
|
|
|
73
73
|
## What data does Splink work best with?
|
|
74
74
|
|
|
@@ -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.dev4"
|
|
4
4
|
description = "Fast probabilistic data linkage at scale"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Robin Linacre", email = "robinlinacre@hotmail.com" },
|
|
@@ -10,7 +10,7 @@ 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 = [
|
|
@@ -44,6 +44,7 @@ demos = [
|
|
|
44
44
|
"nbmake>=1.5.5",
|
|
45
45
|
"pseudopeople>=1.2.6",
|
|
46
46
|
"pytest-xdist>=3.6.1",
|
|
47
|
+
{ "include-group" = "pandas" },
|
|
47
48
|
]
|
|
48
49
|
# dev deliberately doesn't include spark, so we can opt in to different spark versions
|
|
49
50
|
dev = [
|
|
@@ -60,6 +61,7 @@ docs = [
|
|
|
60
61
|
# keep jinja2 relaxed so it doesn't constrain us in rest of dev
|
|
61
62
|
"Jinja2>=3.1.6",
|
|
62
63
|
"mkdocs>=1.6.1",
|
|
64
|
+
"mkdocs-llmstxt>=0.5.0",
|
|
63
65
|
# TODO: maybe we can use this to remove generating dataset docs as separate step
|
|
64
66
|
#"mkdocs-gen-files==0.5.0",
|
|
65
67
|
"mkdocs-material>=9.6.22",
|
|
@@ -72,7 +74,7 @@ docs = [
|
|
|
72
74
|
"neoteroi-mkdocs>=1.1.2",
|
|
73
75
|
"pymdown-extensions>=10.15",
|
|
74
76
|
]
|
|
75
|
-
linting = ["ruff>=0.
|
|
77
|
+
linting = ["ruff>=0.15.18"]
|
|
76
78
|
pandas = ["pandas>=1.3.5", "numpy>=1.19.3"]
|
|
77
79
|
postgres = [
|
|
78
80
|
"psycopg2-binary>=2.9.0",
|
|
@@ -86,22 +88,22 @@ testing-core = [
|
|
|
86
88
|
"pyarrow>=15.0.0",
|
|
87
89
|
"networkx>=2.5.1",
|
|
88
90
|
"pytest-cov>=5.0.0",
|
|
91
|
+
"pytest-xdist>=3.0.0",
|
|
89
92
|
{ "include-group" = "postgres" },
|
|
90
93
|
{ "include-group" = "sqlite" },
|
|
91
|
-
# TODO: temporary measure until we can depanda tests
|
|
92
|
-
{ "include-group" = "pandas" },
|
|
93
94
|
]
|
|
94
95
|
testing = [
|
|
95
96
|
{ "include-group" = "testing-core" },
|
|
96
97
|
{ "include-group" = "spark-4" },
|
|
97
98
|
]
|
|
98
|
-
typechecking = ["mypy
|
|
99
|
+
typechecking = ["mypy>=2.1.0"]
|
|
99
100
|
# we will migrate to ty, but won't happen all at once, so run in parallel
|
|
100
101
|
typechecking-dev = [
|
|
101
102
|
"ty>=0.0.21",
|
|
102
103
|
]
|
|
103
104
|
|
|
104
105
|
[tool.uv]
|
|
106
|
+
exclude-newer = "7 days"
|
|
105
107
|
default-groups = [
|
|
106
108
|
"dev",
|
|
107
109
|
"spark-4"
|
|
@@ -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"]
|
|
@@ -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
|
+
]
|
|
@@ -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,19 +349,17 @@ 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
|
|
|
364
360
|
pipeline.enqueue_sql(sql, "__splink__cartesian_product")
|
|
365
361
|
cartesian_count = linker._db_api.sql_pipeline_to_splink_dataframe(pipeline)
|
|
366
|
-
row_count_df = cartesian_count.
|
|
362
|
+
row_count_df = cartesian_count.as_record_list()
|
|
367
363
|
cartesian_count.drop_table_from_database_and_remove_from_cache()
|
|
368
364
|
|
|
369
365
|
total_labels = calculate_cartesian(row_count_df, link_type)
|
|
@@ -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
|
|
|
@@ -5,16 +5,23 @@ 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
|
-
from splink.internals.misc import
|
|
20
|
+
from splink.internals.misc import (
|
|
21
|
+
dedupe_preserving_order,
|
|
22
|
+
ensure_is_list,
|
|
23
|
+
join_sql_with_union_all,
|
|
24
|
+
)
|
|
18
25
|
from splink.internals.parse_sql import parse_columns_in_sql
|
|
19
26
|
from splink.internals.pipeline import CTEPipeline
|
|
20
27
|
from splink.internals.splink_dataframe import SplinkDataFrame
|
|
@@ -28,6 +35,9 @@ logger = logging.getLogger(__name__)
|
|
|
28
35
|
|
|
29
36
|
# https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports
|
|
30
37
|
if TYPE_CHECKING:
|
|
38
|
+
# Expr only exists in sqlglot >= 30.0.0
|
|
39
|
+
from sqlglot import Expr
|
|
40
|
+
|
|
31
41
|
from splink.internals.settings import LinkTypeLiteralType
|
|
32
42
|
|
|
33
43
|
user_input_link_type_options = Literal["link_only", "link_and_dedupe", "dedupe_only"]
|
|
@@ -117,6 +127,10 @@ class BlockingRule:
|
|
|
117
127
|
def sqlglot_dialect(self) -> str:
|
|
118
128
|
return SplinkDialect.from_string(self._sql_dialect_str).sqlglot_dialect
|
|
119
129
|
|
|
130
|
+
@property
|
|
131
|
+
def requires_blocking_input_materialisation(self) -> bool:
|
|
132
|
+
return False
|
|
133
|
+
|
|
120
134
|
@property
|
|
121
135
|
def sql_dialect(self) -> SplinkDialect:
|
|
122
136
|
return SplinkDialect.from_string(self._sql_dialect_str)
|
|
@@ -203,10 +217,11 @@ class BlockingRule:
|
|
|
203
217
|
on
|
|
204
218
|
({self.blocking_rule_sql})
|
|
205
219
|
{where_condition}
|
|
206
|
-
{
|
|
207
|
-
|
|
208
|
-
unique_id_input_column
|
|
209
|
-
|
|
220
|
+
{
|
|
221
|
+
self.exclude_pairs_generated_by_all_preceding_rules_sql(
|
|
222
|
+
source_dataset_input_column, unique_id_input_column
|
|
223
|
+
)
|
|
224
|
+
}
|
|
210
225
|
"""
|
|
211
226
|
return sql
|
|
212
227
|
|
|
@@ -239,7 +254,7 @@ class BlockingRule:
|
|
|
239
254
|
list of tuples like [(name, name), (substr(name,1,2), substr(name,2,3))]
|
|
240
255
|
"""
|
|
241
256
|
|
|
242
|
-
def remove_table_prefix(tree:
|
|
257
|
+
def remove_table_prefix(tree: Expr) -> Expr:
|
|
243
258
|
for c in tree.find_all(Column):
|
|
244
259
|
del c.args["table"]
|
|
245
260
|
return tree
|
|
@@ -252,7 +267,7 @@ class BlockingRule:
|
|
|
252
267
|
|
|
253
268
|
rmtp = remove_table_prefix
|
|
254
269
|
|
|
255
|
-
keys_de_prefixed: list[tuple[
|
|
270
|
+
keys_de_prefixed: list[tuple[Expr, Expr]] = [
|
|
256
271
|
(rmtp(i), rmtp(j)) for (i, j) in keys_zipped
|
|
257
272
|
]
|
|
258
273
|
|
|
@@ -342,6 +357,8 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
342
357
|
input_tablename_r: str,
|
|
343
358
|
left_chunk: tuple[int, int] | None = None,
|
|
344
359
|
right_chunk: tuple[int, int] | None = None,
|
|
360
|
+
sample_threshold: int | None = None,
|
|
361
|
+
sample_modulus: int | None = None,
|
|
345
362
|
) -> str:
|
|
346
363
|
"""generates a table of the marginal id pairs from the exploded blocking rule
|
|
347
364
|
i.e. pairs are only created that match this blocking rule and NOT any of
|
|
@@ -352,6 +369,9 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
352
369
|
left side records.
|
|
353
370
|
right_chunk: Optional tuple of (chunk_number, total_chunks) for filtering
|
|
354
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.
|
|
355
375
|
"""
|
|
356
376
|
|
|
357
377
|
unique_id_col = unique_id_input_column
|
|
@@ -365,6 +385,8 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
365
385
|
left_chunk=left_chunk,
|
|
366
386
|
right_chunk=right_chunk,
|
|
367
387
|
sql_dialect=self.sql_dialect,
|
|
388
|
+
sample_threshold=sample_threshold,
|
|
389
|
+
sample_modulus=sample_modulus,
|
|
368
390
|
)
|
|
369
391
|
|
|
370
392
|
id_expr_l = _composite_unique_id_from_nodes_sql(unique_id_input_columns, "l")
|
|
@@ -403,6 +425,10 @@ class ExplodingBlockingRule(BlockingRule):
|
|
|
403
425
|
|
|
404
426
|
return "false"
|
|
405
427
|
|
|
428
|
+
@property
|
|
429
|
+
def requires_blocking_input_materialisation(self) -> bool:
|
|
430
|
+
return True
|
|
431
|
+
|
|
406
432
|
def create_blocked_pairs_sql(
|
|
407
433
|
self,
|
|
408
434
|
*,
|
|
@@ -467,6 +493,8 @@ def materialise_exploded_id_tables(
|
|
|
467
493
|
unique_id_input_column: InputColumn,
|
|
468
494
|
left_chunk: tuple[int, int] | None = None,
|
|
469
495
|
right_chunk: tuple[int, int] | None = None,
|
|
496
|
+
sample_threshold: int | None = None,
|
|
497
|
+
sample_modulus: int | None = None,
|
|
470
498
|
) -> list[ExplodingBlockingRule]:
|
|
471
499
|
"""Materialise exploded ID pair tables for exploding blocking rules.
|
|
472
500
|
|
|
@@ -475,6 +503,9 @@ def materialise_exploded_id_tables(
|
|
|
475
503
|
left side records.
|
|
476
504
|
right_chunk: Optional tuple of (chunk_number, total_chunks) for filtering
|
|
477
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.
|
|
478
509
|
"""
|
|
479
510
|
exploding_blocking_rules = [
|
|
480
511
|
br for br in blocking_rules if isinstance(br, ExplodingBlockingRule)
|
|
@@ -556,6 +587,8 @@ def materialise_exploded_id_tables(
|
|
|
556
587
|
input_tablename_r=input_tablename_r,
|
|
557
588
|
left_chunk=left_chunk,
|
|
558
589
|
right_chunk=right_chunk,
|
|
590
|
+
sample_threshold=sample_threshold,
|
|
591
|
+
sample_modulus=sample_modulus,
|
|
559
592
|
)
|
|
560
593
|
|
|
561
594
|
pipeline.enqueue_sql(sql, table_name)
|
|
@@ -668,6 +701,8 @@ def _sql_gen_where_condition(
|
|
|
668
701
|
left_chunk: tuple[int, int] | None = None,
|
|
669
702
|
right_chunk: tuple[int, int] | None = None,
|
|
670
703
|
sql_dialect: "SplinkDialect | None" = None,
|
|
704
|
+
sample_threshold: int | None = None,
|
|
705
|
+
sample_modulus: int | None = None,
|
|
671
706
|
) -> str:
|
|
672
707
|
id_expr_l = _composite_unique_id_from_nodes_sql(unique_id_cols, "l")
|
|
673
708
|
id_expr_r = _composite_unique_id_from_nodes_sql(unique_id_cols, "r")
|
|
@@ -696,6 +731,16 @@ def _sql_gen_where_condition(
|
|
|
696
731
|
unique_id_cols, chunk_num, total_chunks, "r", sql_dialect
|
|
697
732
|
)
|
|
698
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
|
+
|
|
699
744
|
return where_condition
|
|
700
745
|
|
|
701
746
|
|
|
@@ -709,6 +754,8 @@ def block_using_rules_sqls(
|
|
|
709
754
|
unique_id_input_column: InputColumn,
|
|
710
755
|
left_chunk: tuple[int, int] | None = None,
|
|
711
756
|
right_chunk: tuple[int, int] | None = None,
|
|
757
|
+
sample_threshold: int | None = None,
|
|
758
|
+
sample_modulus: int | None = None,
|
|
712
759
|
) -> list[dict[str, str]]:
|
|
713
760
|
"""Use the blocking rules specified in the linker's settings object to
|
|
714
761
|
generate a SQL statement that will create pairwise record comparions
|
|
@@ -739,6 +786,8 @@ def block_using_rules_sqls(
|
|
|
739
786
|
left_chunk=left_chunk,
|
|
740
787
|
right_chunk=right_chunk,
|
|
741
788
|
sql_dialect=sql_dialect,
|
|
789
|
+
sample_threshold=sample_threshold,
|
|
790
|
+
sample_modulus=sample_modulus,
|
|
742
791
|
)
|
|
743
792
|
|
|
744
793
|
# Cover the case where there are no blocking rules
|
|
@@ -760,7 +809,7 @@ def block_using_rules_sqls(
|
|
|
760
809
|
)
|
|
761
810
|
br_sqls.append(sql)
|
|
762
811
|
|
|
763
|
-
sql =
|
|
812
|
+
sql = join_sql_with_union_all(br_sqls)
|
|
764
813
|
|
|
765
814
|
if any(isinstance(br, ExplodingBlockingRule) for br in blocking_rules):
|
|
766
815
|
sqls.append(
|