splink 5.0.0.dev1__tar.gz → 5.0.0.dev2__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 (164) hide show
  1. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/.gitignore +3 -0
  2. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/PKG-INFO +7 -3
  3. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/README.md +2 -0
  4. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/pyproject.toml +57 -15
  5. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/__init__.py +1 -1
  6. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking.py +90 -34
  7. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking_analysis.py +146 -76
  8. splink-5.0.0.dev2/splink/internals/charts.py +714 -0
  9. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/clustering.py +4 -4
  10. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison.py +12 -11
  11. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level.py +54 -33
  12. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level_library.py +15 -2
  13. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_library.py +1 -0
  14. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_vector_values.py +38 -5
  15. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/completeness.py +14 -14
  16. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/connected_components.py +1 -1
  17. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/database_api.py +100 -58
  18. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/datasets/splink_datasets.py +8 -5
  19. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/dialects.py +14 -0
  20. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/duckdb/database_api.py +6 -23
  21. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/duckdb/dataframe.py +28 -7
  22. splink-5.0.0.dev2/splink/internals/duckdb/duckdb_helpers/__init__.py +3 -0
  23. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +17 -0
  24. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/em_training_session.py +48 -21
  25. splink-5.0.0.dev2/splink/internals/estimate_u.py +525 -0
  26. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/expectation_maximisation.py +21 -43
  27. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +54 -13
  28. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/find_brs_with_comparison_counts_below_threshold.py +7 -3
  29. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/input_column.py +7 -4
  30. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/labelling_tool.py +2 -3
  31. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker.py +20 -50
  32. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/evaluation.py +36 -23
  33. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/inference.py +119 -21
  34. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/misc.py +1 -1
  35. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/table_management.py +58 -39
  36. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/training.py +19 -5
  37. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/visualisations.py +72 -69
  38. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/lower_id_on_lhs.py +7 -2
  39. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/m_from_labels.py +1 -2
  40. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/m_training.py +1 -2
  41. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/misc.py +76 -2
  42. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/optimise_cost_of_brs.py +3 -2
  43. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/pipeline.py +45 -5
  44. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/postgres/database_api.py +4 -5
  45. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/postgres/dataframe.py +11 -2
  46. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/profile_data.py +12 -6
  47. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/realtime.py +2 -2
  48. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings.py +60 -34
  49. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/spark/database_api.py +18 -13
  50. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/spark/dataframe.py +31 -3
  51. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/spark/jar_location.py +2 -13
  52. splink-5.0.0.dev2/splink/internals/spark/spark_helpers/version.py +12 -0
  53. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/splink_dataframe.py +60 -3
  54. splink-5.0.0.dev2/splink/internals/splinkdataframe_utils.py +53 -0
  55. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/sqlite/database_api.py +50 -15
  56. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/sqlite/dataframe.py +12 -2
  57. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/term_frequencies.py +135 -137
  58. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/testing.py +8 -3
  59. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/vertically_concatenate.py +158 -5
  60. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/waterfall_chart.py +3 -3
  61. splink-5.0.0.dev1/splink/internals/charts.py +0 -474
  62. splink-5.0.0.dev1/splink/internals/estimate_u.py +0 -230
  63. splink-5.0.0.dev1/splink/internals/files/chart_defs/missingness.json +0 -80
  64. splink-5.0.0.dev1/splink/internals/sqlite/__init__.py +0 -0
  65. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/LICENSE +0 -0
  66. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/backends/duckdb.py +0 -0
  67. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/backends/postgres.py +0 -0
  68. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/backends/spark.py +0 -0
  69. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/backends/sqlite.py +0 -0
  70. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/blocking_analysis.py +0 -0
  71. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/blocking_rule_library.py +0 -0
  72. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/clustering.py +0 -0
  73. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/comparison_level_library.py +0 -0
  74. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/comparison_library.py +0 -0
  75. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/datasets.py +0 -0
  76. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/exploratory.py +0 -0
  77. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/__init__.py +0 -0
  78. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/accuracy.py +0 -0
  79. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/block_from_labels.py +0 -0
  80. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking_rule_creator.py +0 -0
  81. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking_rule_creator_utils.py +0 -0
  82. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/blocking_rule_library.py +0 -0
  83. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/cache_dict_with_logging.py +0 -0
  84. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/chunking.py +0 -0
  85. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/cluster_studio.py +0 -0
  86. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/column_expression.py +0 -0
  87. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_creator.py +0 -0
  88. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level_composition.py +0 -0
  89. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level_creator.py +0 -0
  90. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_level_sql.py +0 -0
  91. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/comparison_vector_distribution.py +0 -0
  92. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/constants.py +0 -0
  93. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/cost_of_blocking_rules.py +0 -0
  94. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/databricks/__init__.py +0 -0
  95. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/databricks/enable_splink.py +0 -0
  96. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/datasets/__init__.py +0 -0
  97. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/datasets/metadata.py +0 -0
  98. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/datasets/utils.py +0 -0
  99. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/default_from_jsonschema.py +0 -0
  100. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/duckdb/__init__.py +0 -0
  101. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/edge_metrics.py +0 -0
  102. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/exceptions.py +0 -0
  103. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
  104. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
  105. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +0 -0
  106. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/comparator_score_chart.json +0 -0
  107. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -0
  108. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/completeness.json +0 -0
  109. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
  110. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
  111. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/match_weights_interactive_history.json +0 -0
  112. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
  113. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
  114. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -0
  115. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/precision_recall.json +0 -0
  116. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
  117. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/profile_data.json +0 -0
  118. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/roc.json +0 -0
  119. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
  120. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
  121. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
  122. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/d3@7.8.5 +0 -0
  123. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
  124. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
  125. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
  126. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/external_js/vega@5.31.0 +0 -0
  127. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/labelling_tool/slt.js +0 -0
  128. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/labelling_tool/template.j2 +0 -0
  129. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/settings_jsonschema.json +0 -0
  130. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/spark_jars/scala-udf-similarity-0.1.2_spark3.x.jar +0 -0
  131. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/spark_jars/scala-udf-similarity-0.2.0_spark4.x.jar +0 -0
  132. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_cluster_studio/cluster_template.j2 +0 -0
  133. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
  134. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
  135. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/splink_comparison_viewer/template.j2 +0 -0
  136. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/files/templates/single_chart_template.html +0 -0
  137. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/find_matches_to_new_records.py +0 -0
  138. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/graph_metrics.py +0 -0
  139. {splink-5.0.0.dev1/splink/internals/duckdb/duckdb_helpers → splink-5.0.0.dev2/splink/internals/linker_components}/__init__.py +0 -0
  140. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/linker_components/clustering.py +0 -0
  141. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/logging_messages.py +0 -0
  142. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/m_u_records_to_parameters.py +0 -0
  143. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/match_weights_histogram.py +0 -0
  144. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/one_to_one_clustering.py +0 -0
  145. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/parse_sql.py +0 -0
  146. {splink-5.0.0.dev1/splink/internals/linker_components → splink-5.0.0.dev2/splink/internals/postgres}/__init__.py +0 -0
  147. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/predict.py +0 -0
  148. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_creator.py +0 -0
  149. {splink-5.0.0.dev1/splink/internals/postgres → splink-5.0.0.dev2/splink/internals/settings_validation}/__init__.py +0 -0
  150. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
  151. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_validation/settings_column_cleaner.py +0 -0
  152. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_validation/settings_validation_log_strings.py +0 -0
  153. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/settings_validation/valid_types.py +0 -0
  154. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/similarity_analysis.py +0 -0
  155. {splink-5.0.0.dev1/splink/internals/settings_validation → splink-5.0.0.dev2/splink/internals/spark}/__init__.py +0 -0
  156. {splink-5.0.0.dev1/splink/internals/spark → splink-5.0.0.dev2/splink/internals/spark/spark_helpers}/__init__.py +0 -0
  157. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
  158. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/splink_comparison_viewer.py +0 -0
  159. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/sql_transform.py +0 -0
  160. {splink-5.0.0.dev1/splink/internals/spark/spark_helpers → splink-5.0.0.dev2/splink/internals/sqlite}/__init__.py +0 -0
  161. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/unique_id_concat.py +0 -0
  162. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/unlinkables.py +0 -0
  163. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/splink/internals/validate_jsonschema.py +0 -0
  164. {splink-5.0.0.dev1 → splink-5.0.0.dev2}/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.dev2
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.dev2"
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.dev2"
60
60
 
61
61
 
62
62
  __all__ = [
@@ -14,12 +14,13 @@ 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 dedupe_preserving_order, ensure_is_list
18
+ from splink.internals.parse_sql import parse_columns_in_sql
18
19
  from splink.internals.pipeline import CTEPipeline
19
20
  from splink.internals.splink_dataframe import SplinkDataFrame
20
21
  from splink.internals.unique_id_concat import _composite_unique_id_from_nodes_sql
21
22
  from splink.internals.vertically_concatenate import (
22
- split_df_concat_with_tf_into_two_tables_sqls,
23
+ select_two_dataset_link_only_input_tables_sqls,
23
24
  vertically_concatenate_sql,
24
25
  )
25
26
 
@@ -76,6 +77,24 @@ def combine_unique_id_input_columns(
76
77
  return unique_id_input_columns
77
78
 
78
79
 
80
+ def _columns_needed_for_blocking(
81
+ blocking_rules: List["BlockingRule"],
82
+ source_dataset_input_column: Optional[InputColumn],
83
+ unique_id_input_column: InputColumn,
84
+ ) -> List[InputColumn]:
85
+ input_columns = combine_unique_id_input_columns(
86
+ source_dataset_input_column, unique_id_input_column
87
+ )
88
+
89
+ for br in blocking_rules:
90
+ parsed_columns = parse_columns_in_sql(
91
+ br.blocking_rule_sql, sqlglot_dialect=br.sqlglot_dialect
92
+ )
93
+ input_columns.extend(br._input_column(col.name) for col in parsed_columns)
94
+
95
+ return dedupe_preserving_order(input_columns)
96
+
97
+
79
98
  class BlockingRule:
80
99
  def __init__(
81
100
  self,
@@ -319,6 +338,8 @@ class ExplodingBlockingRule(BlockingRule):
319
338
  unique_id_input_column: InputColumn,
320
339
  br: BlockingRule,
321
340
  link_type: "LinkTypeLiteralType",
341
+ input_tablename_l: str,
342
+ input_tablename_r: str,
322
343
  left_chunk: tuple[int, int] | None = None,
323
344
  right_chunk: tuple[int, int] | None = None,
324
345
  ) -> str:
@@ -349,11 +370,6 @@ class ExplodingBlockingRule(BlockingRule):
349
370
  id_expr_l = _composite_unique_id_from_nodes_sql(unique_id_input_columns, "l")
350
371
  id_expr_r = _composite_unique_id_from_nodes_sql(unique_id_input_columns, "r")
351
372
 
352
- if link_type == "two_dataset_link_only":
353
- where_condition = (
354
- where_condition + " and l.source_dataset < r.source_dataset"
355
- )
356
-
357
373
  exclude_sql = self.exclude_pairs_generated_by_all_preceding_rules_sql(
358
374
  source_dataset_input_column, unique_id_input_column
359
375
  )
@@ -361,8 +377,8 @@ class ExplodingBlockingRule(BlockingRule):
361
377
  select distinct
362
378
  {id_expr_l} as {unique_id_col.name_l},
363
379
  {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
380
+ from {input_tablename_l} as l
381
+ inner join {input_tablename_r} as r
366
382
  on ({br.blocking_rule_sql})
367
383
  {where_condition}
368
384
  {exclude_sql}
@@ -468,34 +484,65 @@ def materialise_exploded_id_tables(
468
484
  return []
469
485
  exploded_tables = []
470
486
 
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
487
  for br in exploding_blocking_rules:
482
- pipeline = CTEPipeline([nodes_concat])
488
+ pipeline = CTEPipeline()
483
489
  arrays_to_explode_cols = [
484
490
  br._input_column(colname) for colname in br.array_columns_to_explode
485
491
  ]
492
+ input_columns = _columns_needed_for_blocking(
493
+ [*br.preceding_rules, br],
494
+ source_dataset_input_column=source_dataset_input_column,
495
+ unique_id_input_column=unique_id_input_column,
496
+ )
497
+ other_cols = [col for col in input_columns if col not in arrays_to_explode_cols]
486
498
 
487
- other_cols = input_columns_set - set(arrays_to_explode_cols)
499
+ if link_type == "two_dataset_link_only":
500
+ if source_dataset_input_column is None:
501
+ raise ValueError(
502
+ "source_dataset_input_column is required for two_dataset_link_only"
503
+ )
504
+
505
+ left_sql, right_sql = select_two_dataset_link_only_input_tables_sqls(
506
+ splink_df_dict,
507
+ input_columns=input_columns,
508
+ source_dataset_input_column=source_dataset_input_column,
509
+ )
488
510
 
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
- )
511
+ input_tablename_l = "__splink__df_concat_left_unnested"
512
+ input_tablename_r = "__splink__df_concat_right_unnested"
494
513
 
495
- pipeline.enqueue_sql(
496
- expl_sql,
497
- "__splink__df_concat_unnested",
498
- )
514
+ expl_sql_l = db_api.sql_dialect.explode_arrays_sql(
515
+ f"({left_sql})",
516
+ br.array_columns_to_explode,
517
+ [col.name for col in other_cols],
518
+ )
519
+ pipeline.enqueue_sql(expl_sql_l, input_tablename_l)
520
+
521
+ expl_sql_r = db_api.sql_dialect.explode_arrays_sql(
522
+ f"({right_sql})",
523
+ br.array_columns_to_explode,
524
+ [col.name for col in other_cols],
525
+ )
526
+ pipeline.enqueue_sql(expl_sql_r, input_tablename_r)
527
+ else:
528
+ sql = vertically_concatenate_sql(
529
+ splink_df_dict, source_dataset_input_column=source_dataset_input_column
530
+ )
531
+ pipeline.enqueue_sql(sql, "__splink__df_concat")
532
+
533
+ input_tablename_l = "__splink__df_concat_unnested"
534
+ input_tablename_r = "__splink__df_concat_unnested"
535
+
536
+ expl_sql = db_api.sql_dialect.explode_arrays_sql(
537
+ "__splink__df_concat",
538
+ br.array_columns_to_explode,
539
+ [col.name for col in other_cols],
540
+ )
541
+
542
+ pipeline.enqueue_sql(
543
+ expl_sql,
544
+ input_tablename_l,
545
+ )
499
546
 
500
547
  base_name = "__splink__marginal_exploded_ids_blocking_rule"
501
548
  table_name = f"{base_name}_mk_{br.match_key}"
@@ -505,6 +552,8 @@ def materialise_exploded_id_tables(
505
552
  unique_id_input_column=unique_id_input_column,
506
553
  br=br,
507
554
  link_type=link_type,
555
+ input_tablename_l=input_tablename_l,
556
+ input_tablename_r=input_tablename_r,
508
557
  left_chunk=left_chunk,
509
558
  right_chunk=right_chunk,
510
559
  )
@@ -559,11 +608,18 @@ def compute_blocked_pairs_from_concat_with_tf(
559
608
  "source_dataset column"
560
609
  )
561
610
 
562
- sqls = split_df_concat_with_tf_into_two_tables_sqls(
563
- df_concat_with_tf_table_name,
564
- source_dataset_input_column.name,
611
+ input_columns = _columns_needed_for_blocking(
612
+ blocking_rules=blocking_rules,
613
+ source_dataset_input_column=source_dataset_input_column,
614
+ unique_id_input_column=unique_id_input_column,
615
+ )
616
+ left_sql, right_sql = select_two_dataset_link_only_input_tables_sqls(
617
+ splink_df_dict,
618
+ input_columns=input_columns,
619
+ source_dataset_input_column=source_dataset_input_column,
565
620
  )
566
- pipeline.enqueue_list_of_sqls(sqls)
621
+ pipeline.enqueue_sql(left_sql, f"{df_concat_with_tf_table_name}_left")
622
+ pipeline.enqueue_sql(right_sql, f"{df_concat_with_tf_table_name}_right")
567
623
 
568
624
  blocking_input_tablename_l = f"{df_concat_with_tf_table_name}_left"
569
625
  blocking_input_tablename_r = f"{df_concat_with_tf_table_name}_right"