splink 5.0.0.dev3__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.
Files changed (169) hide show
  1. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/PKG-INFO +5 -5
  2. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/README.md +3 -3
  3. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/pyproject.toml +8 -6
  4. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/__init__.py +1 -1
  5. splink-5.0.0.dev4/splink/blocking_analysis.py +11 -0
  6. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/accuracy.py +1 -1
  7. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/blocking.py +45 -8
  8. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/blocking_analysis.py +269 -263
  9. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/cache_dict_with_logging.py +2 -2
  10. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/charts.py +31 -0
  11. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/cluster_studio.py +19 -15
  12. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/clustering.py +2 -2
  13. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/column_expression.py +1 -1
  14. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_level.py +2 -3
  15. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_level_library.py +2 -2
  16. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/completeness.py +2 -2
  17. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/connected_components.py +1 -1
  18. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/database_api.py +63 -19
  19. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/datasets/splink_datasets.py +11 -10
  20. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/dialects.py +65 -76
  21. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/duckdb/database_api.py +5 -0
  22. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/duckdb/dataframe.py +1 -1
  23. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/edge_metrics.py +16 -5
  24. splink-5.0.0.dev4/splink/internals/em_sampling.py +236 -0
  25. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/em_training_session.py +55 -3
  26. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/estimate_u.py +13 -18
  27. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +35 -28
  28. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/match_weights_interactive_history.json +9 -3
  29. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/precision_recall.json +4 -2
  30. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/roc.json +4 -2
  31. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/labelling_tool/slt.js +2 -2
  32. splink-5.0.0.dev4/splink/internals/files/spark_jars/scala-udf-similarity-0.2.1_spark4.x.jar +0 -0
  33. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/labelling_tool.py +26 -9
  34. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/linker.py +23 -190
  35. splink-5.0.0.dev4/splink/internals/linker_components/blocking_analysis.py +164 -0
  36. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/linker_components/evaluation.py +3 -3
  37. splink-5.0.0.dev4/splink/internals/linker_components/inference.py +1550 -0
  38. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/linker_components/misc.py +3 -25
  39. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/linker_components/table_management.py +65 -87
  40. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/linker_components/training.py +36 -9
  41. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/linker_components/visualisations.py +7 -7
  42. splink-5.0.0.dev4/splink/internals/logging_messages.py +6 -0
  43. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/lower_id_on_lhs.py +6 -11
  44. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/match_weights_histogram.py +1 -1
  45. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/misc.py +16 -5
  46. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/one_to_one_clustering.py +1 -1
  47. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/parse_sql.py +4 -4
  48. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/pipeline.py +1 -1
  49. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/postgres/dataframe.py +1 -1
  50. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/profile_data.py +5 -5
  51. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/settings.py +1 -1
  52. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/settings_validation/settings_column_cleaner.py +3 -3
  53. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/settings_validation/settings_validation_log_strings.py +4 -1
  54. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/similarity_analysis.py +10 -10
  55. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/spark/database_api.py +5 -0
  56. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/spark/dataframe.py +5 -5
  57. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/spark/jar_location.py +1 -1
  58. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/splink_dataframe.py +38 -10
  59. splink-5.0.0.dev4/splink/internals/splink_logging.py +56 -0
  60. splink-5.0.0.dev4/splink/internals/splinkdataframe_utils.py +99 -0
  61. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/sqlite/dataframe.py +1 -1
  62. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/term_frequencies.py +4 -5
  63. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/testing.py +2 -2
  64. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/unlinkables.py +1 -1
  65. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/vertically_concatenate.py +3 -3
  66. splink-5.0.0.dev4/splink/logging.py +9 -0
  67. splink-5.0.0.dev3/splink/blocking_analysis.py +0 -13
  68. splink-5.0.0.dev3/splink/internals/cost_of_blocking_rules.py +0 -116
  69. splink-5.0.0.dev3/splink/internals/files/settings_jsonschema.json +0 -317
  70. splink-5.0.0.dev3/splink/internals/find_brs_with_comparison_counts_below_threshold.py +0 -278
  71. splink-5.0.0.dev3/splink/internals/linker_components/inference.py +0 -971
  72. splink-5.0.0.dev3/splink/internals/logging_messages.py +0 -10
  73. splink-5.0.0.dev3/splink/internals/optimise_cost_of_brs.py +0 -219
  74. splink-5.0.0.dev3/splink/internals/splinkdataframe_utils.py +0 -53
  75. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/.gitignore +0 -0
  76. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/LICENSE +0 -0
  77. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/backends/duckdb.py +0 -0
  78. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/backends/postgres.py +0 -0
  79. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/backends/spark.py +0 -0
  80. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/backends/sqlite.py +0 -0
  81. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/blocking_rule_library.py +0 -0
  82. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/clustering.py +0 -0
  83. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/comparison_level_library.py +0 -0
  84. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/comparison_library.py +0 -0
  85. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/datasets.py +0 -0
  86. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/exploratory.py +0 -0
  87. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/__init__.py +0 -0
  88. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/block_from_labels.py +0 -0
  89. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/blocking_rule_creator.py +0 -0
  90. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/blocking_rule_creator_utils.py +0 -0
  91. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/blocking_rule_library.py +0 -0
  92. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/chunking.py +0 -0
  93. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison.py +0 -0
  94. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_creator.py +0 -0
  95. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_level_composition.py +0 -0
  96. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_level_creator.py +0 -0
  97. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_level_sql.py +0 -0
  98. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_library.py +0 -0
  99. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_vector_distribution.py +0 -0
  100. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/comparison_vector_values.py +0 -0
  101. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/constants.py +0 -0
  102. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/databricks/__init__.py +0 -0
  103. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/databricks/enable_splink.py +0 -0
  104. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/datasets/__init__.py +0 -0
  105. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/datasets/metadata.py +0 -0
  106. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/datasets/utils.py +0 -0
  107. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/duckdb/__init__.py +0 -0
  108. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/duckdb/database_api_with_profiling.py +0 -0
  109. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/duckdb/duckdb_helpers/__init__.py +0 -0
  110. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +0 -0
  111. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/exceptions.py +0 -0
  112. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/expectation_maximisation.py +0 -0
  113. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
  114. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
  115. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/comparator_score_chart.json +0 -0
  116. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -0
  117. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/completeness.json +0 -0
  118. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
  119. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
  120. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
  121. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
  122. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -0
  123. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
  124. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/profile_data.json +0 -0
  125. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
  126. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
  127. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
  128. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/external_js/d3@7.8.5 +0 -0
  129. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
  130. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
  131. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
  132. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/external_js/vega@5.31.0 +0 -0
  133. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/labelling_tool/template.j2 +0 -0
  134. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/spark_jars/scala-udf-similarity-0.1.2_spark3.x.jar +0 -0
  135. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/spark_jars/scala-udf-similarity-0.2.0_spark4.x.jar +0 -0
  136. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/splink_cluster_studio/cluster_template.j2 +0 -0
  137. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
  138. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
  139. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/splink_comparison_viewer/template.j2 +0 -0
  140. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +0 -0
  141. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/files/templates/single_chart_template.html +0 -0
  142. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/find_matches_to_new_records.py +0 -0
  143. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/graph_metrics.py +0 -0
  144. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/input_column.py +0 -0
  145. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/linker_components/__init__.py +0 -0
  146. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/linker_components/clustering.py +0 -0
  147. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/m_from_labels.py +0 -0
  148. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/m_training.py +0 -0
  149. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/m_u_records_to_parameters.py +0 -0
  150. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/postgres/__init__.py +0 -0
  151. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/postgres/database_api.py +0 -0
  152. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/predict.py +0 -0
  153. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/realtime.py +0 -0
  154. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/settings_creator.py +0 -0
  155. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/settings_validation/__init__.py +0 -0
  156. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
  157. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/settings_validation/valid_types.py +0 -0
  158. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/spark/__init__.py +0 -0
  159. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/spark/database_api_with_profiling.py +0 -0
  160. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/spark/spark_helpers/__init__.py +0 -0
  161. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
  162. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/spark/spark_helpers/version.py +0 -0
  163. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/splink_comparison_viewer.py +0 -0
  164. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/sql_transform.py +0 -0
  165. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/sqlite/__init__.py +0 -0
  166. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/sqlite/database_api.py +0 -0
  167. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/unique_id_concat.py +0 -0
  168. {splink-5.0.0.dev3 → splink-5.0.0.dev4}/splink/internals/waterfall_chart.py +0 -0
  169. {splink-5.0.0.dev3 → 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.dev3
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.9.0
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
- ![tables showing what splink does](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/what_does_splink_do_1.drawio.png)
63
+ ![Input records that lack a unique person identifier](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/splink_01_input_records.png)
64
64
 
65
65
  Splink predicts which rows link together:
66
66
 
67
- ![tables showing what splink does](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/what_does_splink_do_2.drawio.png)
67
+ ![Pairwise predictions with match probabilities](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/splink_02_pairwise_links.png)
68
68
 
69
69
  and clusters these links to produce an estimated person ID:
70
70
 
71
- ![tables showing what splink does](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/what_does_splink_do_3.drawio.png)
71
+ ![Clusters of linked records forming estimated person IDs](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/splink_03_clusters.png)
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
- ![tables showing what splink does](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/what_does_splink_do_1.drawio.png)
34
+ ![Input records that lack a unique person identifier](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/splink_01_input_records.png)
35
35
 
36
36
  Splink predicts which rows link together:
37
37
 
38
- ![tables showing what splink does](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/what_does_splink_do_2.drawio.png)
38
+ ![Pairwise predictions with match probabilities](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/splink_02_pairwise_links.png)
39
39
 
40
40
  and clusters these links to produce an estimated person ID:
41
41
 
42
- ![tables showing what splink does](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/what_does_splink_do_3.drawio.png)
42
+ ![Clusters of linked records forming estimated person IDs](https://raw.githubusercontent.com/moj-analytical-services/splink/master/docs/img/README/splink_03_clusters.png)
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.dev3"
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.9.0,<4.0.0"
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.4.2,<0.5"]
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==1.9.0"]
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"
@@ -56,7 +56,7 @@ def __getattr__(name):
56
56
  raise AttributeError(f"module 'splink' has no attribute '{name}'") from None
57
57
 
58
58
 
59
- __version__ = "5.0.0.dev3"
59
+ __version__ = "5.0.0.dev4"
60
60
 
61
61
 
62
62
  __all__ = [
@@ -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
+ ]
@@ -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.as_record_dict()
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, Expression, Identifier, Join
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 _chunk_assignment_sql
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
- {self.exclude_pairs_generated_by_all_preceding_rules_sql(
215
- source_dataset_input_column,
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: Expression) -> Expression:
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[Expression, Expression]] = [
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