splink 5.0.0.dev1__tar.gz → 5.0.0.dev3__tar.gz

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