hlink 4.1.0__py3-none-any.whl

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 (260) hide show
  1. hlink/__init__.py +0 -0
  2. hlink/configs/__init__.py +0 -0
  3. hlink/configs/load_config.py +82 -0
  4. hlink/errors.py +16 -0
  5. hlink/linking/README.md +10 -0
  6. hlink/linking/__init__.py +0 -0
  7. hlink/linking/core/__init__.py +0 -0
  8. hlink/linking/core/classifier.py +154 -0
  9. hlink/linking/core/column_mapping.py +53 -0
  10. hlink/linking/core/comparison.py +91 -0
  11. hlink/linking/core/comparison_feature.py +598 -0
  12. hlink/linking/core/dist_table.py +118 -0
  13. hlink/linking/core/model_metrics.py +102 -0
  14. hlink/linking/core/pipeline.py +256 -0
  15. hlink/linking/core/substitutions.py +109 -0
  16. hlink/linking/core/threshold.py +172 -0
  17. hlink/linking/core/transforms.py +565 -0
  18. hlink/linking/hh_matching/__init__.py +6 -0
  19. hlink/linking/hh_matching/hh_matching.py +23 -0
  20. hlink/linking/hh_matching/link_step_block_on_households.py +126 -0
  21. hlink/linking/hh_matching/link_step_filter.py +62 -0
  22. hlink/linking/hh_matching/templates/hh_blocked_matches.sql +17 -0
  23. hlink/linking/hh_matching/templates/hh_potential_matches.sql +26 -0
  24. hlink/linking/hh_model_exploration/__init__.py +6 -0
  25. hlink/linking/hh_model_exploration/hh_model_exploration.py +31 -0
  26. hlink/linking/hh_model_exploration/templates/.gitkeep +0 -0
  27. hlink/linking/hh_training/__init__.py +6 -0
  28. hlink/linking/hh_training/hh_training.py +32 -0
  29. hlink/linking/hh_training/templates/.gitkeep +0 -0
  30. hlink/linking/link_run.py +125 -0
  31. hlink/linking/link_step.py +49 -0
  32. hlink/linking/link_task.py +199 -0
  33. hlink/linking/matching/__init__.py +6 -0
  34. hlink/linking/matching/link_step_explode.py +165 -0
  35. hlink/linking/matching/link_step_match.py +155 -0
  36. hlink/linking/matching/link_step_score.py +210 -0
  37. hlink/linking/matching/matching.py +20 -0
  38. hlink/linking/matching/templates/potential_matches.sql +29 -0
  39. hlink/linking/matching/templates/potential_matches_count.sql +11 -0
  40. hlink/linking/matching/templates/potential_matches_universe.sql +14 -0
  41. hlink/linking/model_exploration/__init__.py +6 -0
  42. hlink/linking/model_exploration/link_step_create_features.py +125 -0
  43. hlink/linking/model_exploration/link_step_ingest_file.py +27 -0
  44. hlink/linking/model_exploration/link_step_train_test_models.py +1017 -0
  45. hlink/linking/model_exploration/model_exploration.py +24 -0
  46. hlink/linking/model_exploration/templates/.gitkeep +0 -0
  47. hlink/linking/preprocessing/__init__.py +6 -0
  48. hlink/linking/preprocessing/link_step_prep_dataframes.py +113 -0
  49. hlink/linking/preprocessing/link_step_register_raw_dfs.py +206 -0
  50. hlink/linking/preprocessing/preprocessing.py +14 -0
  51. hlink/linking/preprocessing/templates/attach_family_col.sql +8 -0
  52. hlink/linking/preprocessing/templates/attach_neighbor_col.sql +8 -0
  53. hlink/linking/preprocessing/templates/attach_related_col.sql +13 -0
  54. hlink/linking/preprocessing/templates/attach_related_cols_as_rows.sql +25 -0
  55. hlink/linking/preprocessing/templates/hh_nbor.sql +11 -0
  56. hlink/linking/preprocessing/templates/hh_nbor_rank.sql +16 -0
  57. hlink/linking/preprocessing/templates/training_data_subset.sql +12 -0
  58. hlink/linking/reporting/__init__.py +6 -0
  59. hlink/linking/reporting/link_step_export_crosswalk.py +75 -0
  60. hlink/linking/reporting/link_step_report_r2_percent_linked.py +97 -0
  61. hlink/linking/reporting/link_step_report_representivity.py +598 -0
  62. hlink/linking/reporting/reporting.py +19 -0
  63. hlink/linking/reporting/templates/.gitkeep +0 -0
  64. hlink/linking/table.py +54 -0
  65. hlink/linking/table_definitions.csv +42 -0
  66. hlink/linking/templates/shared/aggregate_features.sql +22 -0
  67. hlink/linking/templates/shared/all_household_members.sql +32 -0
  68. hlink/linking/templates/shared/attach_variable.sql +8 -0
  69. hlink/linking/templates/shared/drop_links.sql +15 -0
  70. hlink/linking/templates/shared/hh_aggregate_features.sql +27 -0
  71. hlink/linking/templates/shared/includes/all_household_members_selects_a.sql +75 -0
  72. hlink/linking/templates/shared/includes/all_household_members_selects_b.sql +74 -0
  73. hlink/linking/templates/shared/pl_easy_features.sql +9 -0
  74. hlink/linking/templates/shared/potential_matches_base_features.sql +29 -0
  75. hlink/linking/templates/shared/scored_potential_matches.sql +13 -0
  76. hlink/linking/templates/shared/select_columns.sql +13 -0
  77. hlink/linking/templates/shared/tfam_tables.sql +23 -0
  78. hlink/linking/templates/shared/training_features.sql +29 -0
  79. hlink/linking/templates/shared/training_prepped.sql +16 -0
  80. hlink/linking/training/__init__.py +6 -0
  81. hlink/linking/training/link_step_create_comparison_features.py +96 -0
  82. hlink/linking/training/link_step_ingest_file.py +27 -0
  83. hlink/linking/training/link_step_save_model_metadata.py +190 -0
  84. hlink/linking/training/link_step_train_and_save_model.py +80 -0
  85. hlink/linking/training/templates/.gitkeep +0 -0
  86. hlink/linking/training/training.py +26 -0
  87. hlink/linking/transformers/__init__.py +0 -0
  88. hlink/linking/transformers/float_cast_transformer.py +36 -0
  89. hlink/linking/transformers/rename_prob_column.py +14 -0
  90. hlink/linking/transformers/rename_vector_attributes.py +96 -0
  91. hlink/linking/util.py +18 -0
  92. hlink/scripts/__init__.py +0 -0
  93. hlink/scripts/lib/__init__.py +0 -0
  94. hlink/scripts/lib/conf_validations.py +409 -0
  95. hlink/scripts/lib/experimental/__init__.py +0 -0
  96. hlink/scripts/lib/experimental/reporting.py +112 -0
  97. hlink/scripts/lib/experimental/tfam.py +295 -0
  98. hlink/scripts/lib/io.py +91 -0
  99. hlink/scripts/lib/linking_ops.py +70 -0
  100. hlink/scripts/lib/table_ops.py +144 -0
  101. hlink/scripts/lib/util.py +31 -0
  102. hlink/scripts/main.py +230 -0
  103. hlink/scripts/main_loop.py +564 -0
  104. hlink/spark/__init__.py +0 -0
  105. hlink/spark/factory.py +91 -0
  106. hlink/spark/jars/hlink_lib-assembly-1.0.jar +0 -0
  107. hlink/spark/session.py +176 -0
  108. hlink/tests/__init__.py +0 -0
  109. hlink/tests/conf/duplicate_col_maps.toml +14 -0
  110. hlink/tests/conf/duplicate_comp_features.toml +36 -0
  111. hlink/tests/conf/duplicate_feature_sel.toml +24 -0
  112. hlink/tests/conf/integration.toml +70 -0
  113. hlink/tests/conf/missing_datasource_a.toml +1 -0
  114. hlink/tests/conf/missing_datasource_b.toml +4 -0
  115. hlink/tests/conf/no_id_column_a.toml +9 -0
  116. hlink/tests/conf/no_id_column_b.toml +9 -0
  117. hlink/tests/conf/test.json +7 -0
  118. hlink/tests/conf/test1.toml +4 -0
  119. hlink/tests/conf/test_conf_flag_run.json +7 -0
  120. hlink/tests/conf/test_run.json +7 -0
  121. hlink/tests/conf_validations_test.py +225 -0
  122. hlink/tests/config_loader_test.py +67 -0
  123. hlink/tests/conftest.py +1579 -0
  124. hlink/tests/core/__init__.py +0 -0
  125. hlink/tests/core/classifier_test.py +32 -0
  126. hlink/tests/core/column_mapping_test.py +308 -0
  127. hlink/tests/core/comparison_feature_test.py +456 -0
  128. hlink/tests/core/comparison_test.py +109 -0
  129. hlink/tests/core/model_metrics_test.py +200 -0
  130. hlink/tests/core/pipeline_test.py +37 -0
  131. hlink/tests/core/substitutions_test.py +63 -0
  132. hlink/tests/core/threshold_test.py +176 -0
  133. hlink/tests/core/transforms_test.py +413 -0
  134. hlink/tests/hh_matching_test.py +474 -0
  135. hlink/tests/hh_model_exploration_test.py +153 -0
  136. hlink/tests/hh_training_test.py +538 -0
  137. hlink/tests/input_data/19thc_nativity_test_hhs.csv +16 -0
  138. hlink/tests/input_data/19thc_nativity_test_hhs_a.csv +18 -0
  139. hlink/tests/input_data/19thc_nativity_test_hhs_b.csv +17 -0
  140. hlink/tests/input_data/birthyr_replace.csv +6 -0
  141. hlink/tests/input_data/calc_mfbpl_a.csv +16 -0
  142. hlink/tests/input_data/calc_mfbpl_b.csv +16 -0
  143. hlink/tests/input_data/conf_validation/a.csv +1 -0
  144. hlink/tests/input_data/conf_validation/b.csv +1 -0
  145. hlink/tests/input_data/conf_validation/empty.csv +0 -0
  146. hlink/tests/input_data/county_distances.csv +5 -0
  147. hlink/tests/input_data/crosswalk/hh_predicted_matches.csv +2 -0
  148. hlink/tests/input_data/crosswalk/predicted_matches.csv +6 -0
  149. hlink/tests/input_data/crosswalk/raw_df_a.csv +28 -0
  150. hlink/tests/input_data/crosswalk/raw_df_b.csv +28 -0
  151. hlink/tests/input_data/female.csv +4 -0
  152. hlink/tests/input_data/ha_source.csv +12 -0
  153. hlink/tests/input_data/handle_null.csv +10 -0
  154. hlink/tests/input_data/hb_source.csv +15 -0
  155. hlink/tests/input_data/hh_matching_a.csv +101 -0
  156. hlink/tests/input_data/hh_matching_b.csv +110 -0
  157. hlink/tests/input_data/hh_predicted_matches_reporting.csv +11 -0
  158. hlink/tests/input_data/hh_year_a.csv +7 -0
  159. hlink/tests/input_data/hh_year_b.csv +9 -0
  160. hlink/tests/input_data/hhpm_agg_test.csv +381 -0
  161. hlink/tests/input_data/households_b.parquet +0 -0
  162. hlink/tests/input_data/integration_a.csv +4 -0
  163. hlink/tests/input_data/integration_b.csv +4 -0
  164. hlink/tests/input_data/jw_blocking_test_a.csv +7 -0
  165. hlink/tests/input_data/jw_blocking_test_b.csv +2 -0
  166. hlink/tests/input_data/male.csv +2 -0
  167. hlink/tests/input_data/matched_men.csv +21 -0
  168. hlink/tests/input_data/matching_or_group_test_a.csv +59 -0
  169. hlink/tests/input_data/matching_or_group_test_b.csv +27 -0
  170. hlink/tests/input_data/matching_test_a.csv +59 -0
  171. hlink/tests/input_data/matching_test_b.csv +27 -0
  172. hlink/tests/input_data/nativity_test_data_a.csv +7 -0
  173. hlink/tests/input_data/nativity_test_data_b.csv +7 -0
  174. hlink/tests/input_data/new_hh_test_td.csv +199 -0
  175. hlink/tests/input_data/popularity.csv +9 -0
  176. hlink/tests/input_data/potential_matches.csv +25 -0
  177. hlink/tests/input_data/potential_matches_agg.csv +31 -0
  178. hlink/tests/input_data/potential_matches_ids_only.csv +10 -0
  179. hlink/tests/input_data/potential_matches_sql_condition_marst_warn.csv +37 -0
  180. hlink/tests/input_data/predicted_matches_reporting.csv +11 -0
  181. hlink/tests/input_data/predicted_matches_test.csv +6 -0
  182. hlink/tests/input_data/prepped_df_a_agg.csv +25 -0
  183. hlink/tests/input_data/prepped_df_b_agg.csv +31 -0
  184. hlink/tests/input_data/prepped_df_reporting.csv +28 -0
  185. hlink/tests/input_data/raw_df_reporting.csv +28 -0
  186. hlink/tests/input_data/region.csv +161 -0
  187. hlink/tests/input_data/regioncode.csv +52 -0
  188. hlink/tests/input_data/rel_rows_test_a.csv +28 -0
  189. hlink/tests/input_data/rel_rows_test_b.csv +34 -0
  190. hlink/tests/input_data/reporting_hh_predicted_matches.csv +10 -0
  191. hlink/tests/input_data/reporting_predicted_matches.csv +15 -0
  192. hlink/tests/input_data/reporting_prepped_df_a.csv +35 -0
  193. hlink/tests/input_data/representivity.csv +92 -0
  194. hlink/tests/input_data/scored_matches_household_test.csv +7 -0
  195. hlink/tests/input_data/scored_matches_test_data.csv +7 -0
  196. hlink/tests/input_data/sql_condition_marst_warn_a.csv +37 -0
  197. hlink/tests/input_data/sql_condition_marst_warn_b.csv +37 -0
  198. hlink/tests/input_data/statedist.csv +2602 -0
  199. hlink/tests/input_data/street_abbrevs.csv +544 -0
  200. hlink/tests/input_data/street_abbrevs_most_common.csv +72 -0
  201. hlink/tests/input_data/test_csv_data_a.csv +4 -0
  202. hlink/tests/input_data/test_csv_data_b.csv +4 -0
  203. hlink/tests/input_data/test_parquet_data_a.parquet/._SUCCESS.crc +0 -0
  204. hlink/tests/input_data/test_parquet_data_a.parquet/.part-00000-8465f06e-9bb0-4817-81eb-813ccf07429a-c000.snappy.parquet.crc +0 -0
  205. hlink/tests/input_data/test_parquet_data_a.parquet/_SUCCESS +0 -0
  206. hlink/tests/input_data/test_parquet_data_a.parquet/part-00000-8465f06e-9bb0-4817-81eb-813ccf07429a-c000.snappy.parquet +0 -0
  207. hlink/tests/input_data/test_parquet_data_b.parquet/._SUCCESS.crc +0 -0
  208. hlink/tests/input_data/test_parquet_data_b.parquet/.part-00000-bb515275-04b4-4a16-80f1-5cc21450b93f-c000.snappy.parquet.crc +0 -0
  209. hlink/tests/input_data/test_parquet_data_b.parquet/_SUCCESS +0 -0
  210. hlink/tests/input_data/test_parquet_data_b.parquet/part-00000-bb515275-04b4-4a16-80f1-5cc21450b93f-c000.snappy.parquet +0 -0
  211. hlink/tests/input_data/test_prepped_data_a.csv +4 -0
  212. hlink/tests/input_data/test_prepped_data_b.csv +4 -0
  213. hlink/tests/input_data/test_street_names_data.csv +10 -0
  214. hlink/tests/input_data/threshold_ratio_test.csv +17 -0
  215. hlink/tests/input_data/threshold_ratio_test_data_2.csv +14 -0
  216. hlink/tests/input_data/training_data.csv +10 -0
  217. hlink/tests/input_data/training_data_doubled.csv +19 -0
  218. hlink/tests/input_data/training_data_households.csv +59 -0
  219. hlink/tests/input_data/training_data_households.parquet +0 -0
  220. hlink/tests/input_data/training_data_long.csv +25 -0
  221. hlink/tests/input_data/training_data_long_a.csv +25 -0
  222. hlink/tests/input_data/training_data_long_b.csv +25 -0
  223. hlink/tests/integration_score_with_trained_models_test.py +1417 -0
  224. hlink/tests/integration_test.py +51 -0
  225. hlink/tests/linking_util_test.py +12 -0
  226. hlink/tests/logging_test.py +14 -0
  227. hlink/tests/main_loop_test.py +147 -0
  228. hlink/tests/markers.py +31 -0
  229. hlink/tests/matching_blocking_explode_test.py +284 -0
  230. hlink/tests/matching_comparison_features_test.py +1197 -0
  231. hlink/tests/matching_geo_distance_test.py +427 -0
  232. hlink/tests/matching_potential_matches_test.py +73 -0
  233. hlink/tests/matching_potential_matches_universe_test.py +70 -0
  234. hlink/tests/matching_scoring_test.py +174 -0
  235. hlink/tests/matching_test.py +215 -0
  236. hlink/tests/model_exploration_test.py +1016 -0
  237. hlink/tests/plugins/__init__.py +0 -0
  238. hlink/tests/plugins/datasources.py +1037 -0
  239. hlink/tests/plugins/external_data_paths.py +409 -0
  240. hlink/tests/preprocessing_test.py +1884 -0
  241. hlink/tests/reporting_test.py +117 -0
  242. hlink/tests/scala_udf_test.py +29 -0
  243. hlink/tests/spark_connection_test.py +56 -0
  244. hlink/tests/spark_factory_test.py +52 -0
  245. hlink/tests/table_test.py +65 -0
  246. hlink/tests/training_test.py +718 -0
  247. hlink/tests/transformers_test.py +77 -0
  248. hlink/tests/validation_data/crosswalks/crosswalk.csv +7 -0
  249. hlink/tests/validation_data/crosswalks/crosswalk_with_round.csv +7 -0
  250. hlink/tests/validation_data/training_p_all.parquet/._SUCCESS.crc +0 -0
  251. hlink/tests/validation_data/training_p_all.parquet/.part-00000-6c9e5760-174a-4652-b687-6823087ed5bf-c000.snappy.parquet.crc +0 -0
  252. hlink/tests/validation_data/training_p_all.parquet/_SUCCESS +0 -0
  253. hlink/tests/validation_data/training_p_all.parquet/part-00000-6c9e5760-174a-4652-b687-6823087ed5bf-c000.snappy.parquet +0 -0
  254. hlink-4.1.0.dist-info/METADATA +646 -0
  255. hlink-4.1.0.dist-info/RECORD +260 -0
  256. hlink-4.1.0.dist-info/WHEEL +5 -0
  257. hlink-4.1.0.dist-info/entry_points.txt +2 -0
  258. hlink-4.1.0.dist-info/licenses/LICENSE.txt +373 -0
  259. hlink-4.1.0.dist-info/licenses/NOTICE.txt +16 -0
  260. hlink-4.1.0.dist-info/top_level.txt +1 -0
hlink/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,82 @@
1
+ # This file is part of the ISRDI's hlink.
2
+ # For copyright and licensing information, see the NOTICE and LICENSE files
3
+ # in this project's top-level directory, and also on-line at:
4
+ # https://github.com/ipums/hlink
5
+
6
+ from pathlib import Path
7
+ from typing import Any
8
+ import json
9
+ import toml
10
+ import tomli
11
+
12
+ from hlink.errors import UsageError
13
+
14
+
15
+ def load_conf_file(
16
+ conf_name: str, *, use_legacy_toml_parser: bool = False
17
+ ) -> tuple[Path, dict[str, Any]]:
18
+ """Flexibly load a config file.
19
+
20
+ Given a path `conf_name`, look for a file at that path. If that file
21
+ exists and has a '.toml' extension or a '.json' extension, load it and
22
+ return its contents. If it doesn't exist, look for a file with the same
23
+ name with a '.toml' extension added and load it if it exists. Then do the
24
+ same for a file with a '.json' extension added.
25
+
26
+ `use_legacy_toml_parser` tells this function to use the legacy TOML library
27
+ which hlink used to use instead of the current default. This is provided
28
+ for backwards compatibility. Some previously written config files may
29
+ depend on bugs in the legacy TOML library, making it hard to migrate to the
30
+ new TOML v1.0 compliant parser. It is strongly recommended that new code
31
+ and config files use the default parser. Old code and config files should
32
+ also try to migrate to the default parser when possible.
33
+
34
+ Args:
35
+ conf_name: the file to look for
36
+ use_legacy_toml_parser: (Not Recommended) Use the legacy, buggy TOML
37
+ parser instead of the default parser.
38
+
39
+ Returns:
40
+ a tuple (absolute path to the config file, contents of the config file)
41
+
42
+ Raises:
43
+ FileNotFoundError: if none of the three checked files exist
44
+ UsageError: if the file at path `conf_name` exists, but it doesn't have a '.toml' or '.json' extension
45
+ """
46
+ candidate_files = [
47
+ Path(conf_name),
48
+ Path(conf_name + ".toml"),
49
+ Path(conf_name + ".json"),
50
+ ]
51
+
52
+ existing_files = filter((lambda file: file.exists()), candidate_files)
53
+
54
+ for file in existing_files:
55
+ if file.suffix == ".toml":
56
+ # Legacy support for using the "toml" library instead of "tomli".
57
+ #
58
+ # Eventually we should remove use_legacy_toml_parser and just use
59
+ # tomli or Python's standard library tomllib, which is available in
60
+ # Python 3.11+.
61
+ if use_legacy_toml_parser:
62
+ with open(file) as f:
63
+ conf = toml.load(f)
64
+ return file.absolute(), conf
65
+ else:
66
+ with open(file, "rb") as f:
67
+ conf = tomli.load(f)
68
+ return file.absolute(), conf
69
+
70
+ if file.suffix == ".json":
71
+ with open(file) as f:
72
+ conf = json.load(f)
73
+ return file.absolute(), conf
74
+
75
+ raise UsageError(
76
+ f"The file {file} exists, but it doesn't have a '.toml' or '.json' extension."
77
+ )
78
+
79
+ candidate_files_str = ", ".join(map(str, candidate_files))
80
+ raise FileNotFoundError(
81
+ f"Couldn't find any of these three files: {candidate_files_str}"
82
+ )
hlink/errors.py ADDED
@@ -0,0 +1,16 @@
1
+ # This file is part of the ISRDI's hlink.
2
+ # For copyright and licensing information, see the NOTICE and LICENSE files
3
+ # in this project's top-level directory, and also on-line at:
4
+ # https://github.com/ipums/hlink
5
+
6
+
7
+ class SparkError(Exception):
8
+ """Catch any exceptions from Spark"""
9
+
10
+
11
+ class UsageError(Exception):
12
+ """Incorrectly specified options"""
13
+
14
+
15
+ class DataError(Exception):
16
+ """There is an issue in the source data that will cause a problem."""
@@ -0,0 +1,10 @@
1
+ ## Overview
2
+
3
+ There is one base class,`LinkTask`, defined in the file: `link_task.py`. Each subpackage contains a class which inherits from the base `LinkTask`.
4
+
5
+ All classes inheriting from `LinkTask`, directly correspond to a task that the user can do. For example, the `Preprocessing` class contains all the code for the `preprocessing` user task.
6
+
7
+ ## Templates
8
+
9
+ The `templates` directory in each subpackage contains SQL file templates that are written using [jinja2](http://jinja.pocoo.org/docs/2.10/templates/). Putting all the sql in this directory allows for a seperation between the python code and the SQL code used. Using jinja2 templating allows for reuse of an sql file with slightly different parameters.
10
+
File without changes
File without changes
@@ -0,0 +1,154 @@
1
+ # This file is part of the ISRDI's hlink.
2
+ # For copyright and licensing information, see the NOTICE and LICENSE files
3
+ # in this project's top-level directory, and also on-line at:
4
+ # https://github.com/ipums/hlink
5
+
6
+ from typing import Any
7
+
8
+ from pyspark.ml.feature import SQLTransformer
9
+ from pyspark.ml.regression import GeneralizedLinearRegression
10
+ from pyspark.ml.classification import (
11
+ RandomForestClassifier,
12
+ LogisticRegression,
13
+ DecisionTreeClassifier,
14
+ GBTClassifier,
15
+ )
16
+ import hlink.linking.transformers.rename_prob_column
17
+
18
+ try:
19
+ import synapse.ml.lightgbm
20
+ except ModuleNotFoundError:
21
+ _lightgbm_available = False
22
+ else:
23
+ _lightgbm_available = True
24
+
25
+ try:
26
+ import xgboost.spark
27
+ except ModuleNotFoundError:
28
+ _xgboost_available = False
29
+ else:
30
+ _xgboost_available = True
31
+
32
+
33
+ def choose_classifier(model_type: str, params: dict[str, Any], dep_var: str):
34
+ """Given a model type and hyper-parameters for the model, return a
35
+ classifier of that type with those hyper-parameters, along with a
36
+ post-classification transformer to run after classification.
37
+
38
+ The post-classification transformer standardizes the output of the
39
+ classifier for further processing. For example, some classifiers create
40
+ models that output a probability array of [P(dep_var=0), P(dep_var=1)], and
41
+ the post-classification transformer extracts the single float P(dep_var=1)
42
+ as the probability for these models.
43
+
44
+ Parameters
45
+ ----------
46
+ model_type
47
+ the type of model, which may be random_forest, probit,
48
+ logistic_regression, decision_tree, gradient_boosted_trees, lightgbm
49
+ (requires the 'lightgbm' extra), or xgboost (requires the 'xgboost'
50
+ extra)
51
+ params
52
+ a dictionary of hyper-parameters for the model
53
+ dep_var
54
+ the dependent variable for the model, sometimes also called the "label"
55
+
56
+ Returns
57
+ -------
58
+ The classifier and a transformer to be used after classification, as a tuple.
59
+ """
60
+ post_transformer = SQLTransformer(statement="SELECT * FROM __THIS__")
61
+ features_vector = "features_vector"
62
+ if model_type == "random_forest":
63
+ classifier = RandomForestClassifier(
64
+ **params,
65
+ labelCol=dep_var,
66
+ featuresCol=features_vector,
67
+ seed=2133,
68
+ probabilityCol="probability_array",
69
+ )
70
+ post_transformer = SQLTransformer(
71
+ statement="SELECT *, parseProbVector(probability_array, 1) as probability FROM __THIS__"
72
+ )
73
+
74
+ elif model_type == "probit":
75
+ classifier = GeneralizedLinearRegression(
76
+ family="binomial",
77
+ link="probit",
78
+ labelCol=dep_var,
79
+ featuresCol=features_vector,
80
+ predictionCol="probability",
81
+ )
82
+
83
+ elif model_type == "logistic_regression":
84
+ classifier = LogisticRegression(
85
+ **params,
86
+ featuresCol=features_vector,
87
+ labelCol=dep_var,
88
+ predictionCol="prediction",
89
+ probabilityCol="probability_array",
90
+ )
91
+ post_transformer = SQLTransformer(
92
+ statement="SELECT *, parseProbVector(probability_array, 1) as probability FROM __THIS__"
93
+ )
94
+
95
+ elif model_type == "decision_tree":
96
+ classifier = DecisionTreeClassifier(
97
+ **params,
98
+ featuresCol=features_vector,
99
+ labelCol=dep_var,
100
+ probabilityCol="probability_array",
101
+ seed=2133,
102
+ )
103
+ post_transformer = SQLTransformer(
104
+ statement="SELECT *, parseProbVector(probability_array, 1) as probability FROM __THIS__"
105
+ )
106
+
107
+ elif model_type == "gradient_boosted_trees":
108
+ classifier = GBTClassifier(
109
+ **params,
110
+ featuresCol=features_vector,
111
+ labelCol=dep_var,
112
+ seed=2133,
113
+ )
114
+ post_transformer = (
115
+ hlink.linking.transformers.rename_prob_column.RenameProbColumn()
116
+ )
117
+ elif model_type == "lightgbm":
118
+ if not _lightgbm_available:
119
+ raise ModuleNotFoundError(
120
+ "To use the 'lightgbm' model type, you need to install the synapseml "
121
+ "Python package, which provides LightGBM-Spark integration, and "
122
+ "its dependencies. Try installing hlink with the lightgbm extra: "
123
+ "\n\n pip install hlink[lightgbm]"
124
+ )
125
+ classifier = synapse.ml.lightgbm.LightGBMClassifier(
126
+ **params,
127
+ featuresCol=features_vector,
128
+ labelCol=dep_var,
129
+ probabilityCol="probability_array",
130
+ )
131
+ post_transformer = SQLTransformer(
132
+ statement="SELECT *, parseProbVector(probability_array, 1) as probability FROM __THIS__"
133
+ )
134
+ elif model_type == "xgboost":
135
+ if not _xgboost_available:
136
+ raise ModuleNotFoundError(
137
+ "To use the experimental 'xgboost' model type, you need to install "
138
+ "the xgboost library and its dependencies. Try installing hlink with "
139
+ "the xgboost extra:\n\n pip install hlink[xgboost]"
140
+ )
141
+ classifier = xgboost.spark.SparkXGBClassifier(
142
+ **params,
143
+ features_col=features_vector,
144
+ label_col=dep_var,
145
+ probability_col="probability_array",
146
+ )
147
+ post_transformer = SQLTransformer(
148
+ statement="SELECT *, parseProbVector(probability_array, 1) as probability FROM __THIS__"
149
+ )
150
+ else:
151
+ raise ValueError(
152
+ "Model type not recognized! Please check your config, reload, and try again."
153
+ )
154
+ return classifier, post_transformer
@@ -0,0 +1,53 @@
1
+ # This file is part of the ISRDI's hlink.
2
+ # For copyright and licensing information, see the NOTICE and LICENSE files
3
+ # in this project's top-level directory, and also on-line at:
4
+ # https://github.com/ipums/hlink
5
+ from typing import Any
6
+
7
+ from pyspark.sql.functions import col, lit
8
+ from pyspark.sql import DataFrame
9
+ import hlink.linking.core.transforms as transforms_core
10
+
11
+
12
+ def select_column_mapping(
13
+ column_mapping: dict[str, Any],
14
+ df_selected: DataFrame,
15
+ is_a: bool,
16
+ column_selects: list[str],
17
+ ) -> tuple[DataFrame, list[str]]:
18
+ name = column_mapping["column_name"]
19
+ if "override_column_a" in column_mapping and is_a:
20
+ override_name = column_mapping["override_column_a"]
21
+ column_select = col(override_name)
22
+ if "override_transforms" in column_mapping:
23
+ for transform in column_mapping["override_transforms"]:
24
+ column_select = transforms_core.apply_transform(
25
+ column_select, transform, is_a
26
+ )
27
+ elif "override_column_b" in column_mapping and not is_a:
28
+ override_name = column_mapping["override_column_b"]
29
+ column_select = col(override_name)
30
+ if "override_transforms" in column_mapping:
31
+ for transform in column_mapping["override_transforms"]:
32
+ column_select = transforms_core.apply_transform(
33
+ column_select, transform, is_a
34
+ )
35
+ elif "set_value_column_a" in column_mapping and is_a:
36
+ value_to_set = column_mapping["set_value_column_a"]
37
+ column_select = lit(value_to_set)
38
+ elif "set_value_column_b" in column_mapping and not is_a:
39
+ value_to_set = column_mapping["set_value_column_b"]
40
+ column_select = lit(value_to_set)
41
+ elif "transforms" in column_mapping:
42
+ column_select = col(name)
43
+ for transform in column_mapping["transforms"]:
44
+ column_select = transforms_core.apply_transform(
45
+ column_select, transform, is_a
46
+ )
47
+ else:
48
+ column_select = col(name)
49
+
50
+ alias = column_mapping["alias"] if "alias" in column_mapping else name
51
+
52
+ column_selects.append(alias)
53
+ return df_selected.withColumn(alias, column_select), column_selects
@@ -0,0 +1,91 @@
1
+ # This file is part of the ISRDI's hlink.
2
+ # For copyright and licensing information, see the NOTICE and LICENSE files
3
+ # in this project's top-level directory, and also on-line at:
4
+ # https://github.com/ipums/hlink
5
+ from typing import Any
6
+
7
+ import hlink.linking.core.comparison_feature as comparison_feature_core
8
+
9
+
10
+ def get_comparison_leaves(comp: dict[str, Any]) -> list[dict[str, Any]]:
11
+ comp_leaves = []
12
+
13
+ def _get_comp_leaf(comp: dict[str, Any], comp_leaves: list[dict[str, Any]]) -> None:
14
+ if "comp_a" in comp:
15
+ _get_comp_leaf(comp["comp_a"], comp_leaves)
16
+ _get_comp_leaf(comp["comp_b"], comp_leaves)
17
+
18
+ else:
19
+ comp_leaves.append(comp)
20
+
21
+ if "comp_a" in comp:
22
+ _get_comp_leaf(comp["comp_a"], comp_leaves)
23
+ _get_comp_leaf(comp["comp_b"], comp_leaves)
24
+
25
+ elif "secondary" in comp:
26
+ _get_comp_leaf(comp["threshold_a"], comp_leaves)
27
+ _get_comp_leaf(comp["threshold_b"], comp_leaves)
28
+
29
+ else:
30
+ _get_comp_leaf(comp, comp_leaves)
31
+
32
+ return comp_leaves
33
+
34
+
35
+ def generate_comparisons(
36
+ comp: dict[str, Any], features: list[dict[str, Any]], id_col: str
37
+ ) -> str:
38
+ """Creates the comparison SQL clause given a comparison and a list of comparison features.
39
+
40
+ Parameters
41
+ ----------
42
+ comp:
43
+ the config dictionary containing the comparison definition
44
+ features:
45
+ the config list containing the comparison features
46
+ id_col:
47
+ the id column
48
+
49
+ Returns
50
+ -------
51
+ The sql clause to be used for comparison filtering after blocking.
52
+ """
53
+ if comp != {}:
54
+ if "comp_a" in comp:
55
+ comp_a_clause = generate_comparisons(comp["comp_a"], features, id_col)
56
+ comp_b_clause = generate_comparisons(comp["comp_b"], features, id_col)
57
+ if comp["operator"] == "AND":
58
+ return f"""
59
+ ({comp_a_clause} AND {comp_b_clause})
60
+ """
61
+ elif comp["operator"] == "OR":
62
+ return f"""
63
+ ({comp_a_clause} OR {comp_b_clause})
64
+ """
65
+ elif "secondary" in comp:
66
+ comp_a = comp["threshold_a"]
67
+ comp_a_clause = f"{comp_a['feature_name']} >= {comp_a['threshold']}"
68
+ comp_b = comp["threshold_b"]
69
+ comp_b_clause = f"{comp_b['feature_name']} >= {comp_b['threshold']}"
70
+ if comp["operator"] == "AND":
71
+ return f"({comp_a_clause} AND {comp_b_clause})"
72
+
73
+ else:
74
+ if "column_name" in comp:
75
+ col = comp["column_name"]
76
+ else:
77
+ col = comparison_feature_core.generate_comparison_feature(
78
+ [f for f in features if f["alias"] == comp["feature_name"]][0],
79
+ id_col,
80
+ )
81
+ if "comparison_type" in comp:
82
+ comp_type = comp["comparison_type"]
83
+ if comp_type == "threshold":
84
+ if comp.get("threshold_expr", False):
85
+ return f"{col} {comp['threshold_expr']}"
86
+ else:
87
+ return f"{col} >= {comp['threshold']}"
88
+ else:
89
+ return f"{col}"
90
+ else:
91
+ return ""