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.
- hlink/__init__.py +0 -0
- hlink/configs/__init__.py +0 -0
- hlink/configs/load_config.py +82 -0
- hlink/errors.py +16 -0
- hlink/linking/README.md +10 -0
- hlink/linking/__init__.py +0 -0
- hlink/linking/core/__init__.py +0 -0
- hlink/linking/core/classifier.py +154 -0
- hlink/linking/core/column_mapping.py +53 -0
- hlink/linking/core/comparison.py +91 -0
- hlink/linking/core/comparison_feature.py +598 -0
- hlink/linking/core/dist_table.py +118 -0
- hlink/linking/core/model_metrics.py +102 -0
- hlink/linking/core/pipeline.py +256 -0
- hlink/linking/core/substitutions.py +109 -0
- hlink/linking/core/threshold.py +172 -0
- hlink/linking/core/transforms.py +565 -0
- hlink/linking/hh_matching/__init__.py +6 -0
- hlink/linking/hh_matching/hh_matching.py +23 -0
- hlink/linking/hh_matching/link_step_block_on_households.py +126 -0
- hlink/linking/hh_matching/link_step_filter.py +62 -0
- hlink/linking/hh_matching/templates/hh_blocked_matches.sql +17 -0
- hlink/linking/hh_matching/templates/hh_potential_matches.sql +26 -0
- hlink/linking/hh_model_exploration/__init__.py +6 -0
- hlink/linking/hh_model_exploration/hh_model_exploration.py +31 -0
- hlink/linking/hh_model_exploration/templates/.gitkeep +0 -0
- hlink/linking/hh_training/__init__.py +6 -0
- hlink/linking/hh_training/hh_training.py +32 -0
- hlink/linking/hh_training/templates/.gitkeep +0 -0
- hlink/linking/link_run.py +125 -0
- hlink/linking/link_step.py +49 -0
- hlink/linking/link_task.py +199 -0
- hlink/linking/matching/__init__.py +6 -0
- hlink/linking/matching/link_step_explode.py +165 -0
- hlink/linking/matching/link_step_match.py +155 -0
- hlink/linking/matching/link_step_score.py +210 -0
- hlink/linking/matching/matching.py +20 -0
- hlink/linking/matching/templates/potential_matches.sql +29 -0
- hlink/linking/matching/templates/potential_matches_count.sql +11 -0
- hlink/linking/matching/templates/potential_matches_universe.sql +14 -0
- hlink/linking/model_exploration/__init__.py +6 -0
- hlink/linking/model_exploration/link_step_create_features.py +125 -0
- hlink/linking/model_exploration/link_step_ingest_file.py +27 -0
- hlink/linking/model_exploration/link_step_train_test_models.py +1017 -0
- hlink/linking/model_exploration/model_exploration.py +24 -0
- hlink/linking/model_exploration/templates/.gitkeep +0 -0
- hlink/linking/preprocessing/__init__.py +6 -0
- hlink/linking/preprocessing/link_step_prep_dataframes.py +113 -0
- hlink/linking/preprocessing/link_step_register_raw_dfs.py +206 -0
- hlink/linking/preprocessing/preprocessing.py +14 -0
- hlink/linking/preprocessing/templates/attach_family_col.sql +8 -0
- hlink/linking/preprocessing/templates/attach_neighbor_col.sql +8 -0
- hlink/linking/preprocessing/templates/attach_related_col.sql +13 -0
- hlink/linking/preprocessing/templates/attach_related_cols_as_rows.sql +25 -0
- hlink/linking/preprocessing/templates/hh_nbor.sql +11 -0
- hlink/linking/preprocessing/templates/hh_nbor_rank.sql +16 -0
- hlink/linking/preprocessing/templates/training_data_subset.sql +12 -0
- hlink/linking/reporting/__init__.py +6 -0
- hlink/linking/reporting/link_step_export_crosswalk.py +75 -0
- hlink/linking/reporting/link_step_report_r2_percent_linked.py +97 -0
- hlink/linking/reporting/link_step_report_representivity.py +598 -0
- hlink/linking/reporting/reporting.py +19 -0
- hlink/linking/reporting/templates/.gitkeep +0 -0
- hlink/linking/table.py +54 -0
- hlink/linking/table_definitions.csv +42 -0
- hlink/linking/templates/shared/aggregate_features.sql +22 -0
- hlink/linking/templates/shared/all_household_members.sql +32 -0
- hlink/linking/templates/shared/attach_variable.sql +8 -0
- hlink/linking/templates/shared/drop_links.sql +15 -0
- hlink/linking/templates/shared/hh_aggregate_features.sql +27 -0
- hlink/linking/templates/shared/includes/all_household_members_selects_a.sql +75 -0
- hlink/linking/templates/shared/includes/all_household_members_selects_b.sql +74 -0
- hlink/linking/templates/shared/pl_easy_features.sql +9 -0
- hlink/linking/templates/shared/potential_matches_base_features.sql +29 -0
- hlink/linking/templates/shared/scored_potential_matches.sql +13 -0
- hlink/linking/templates/shared/select_columns.sql +13 -0
- hlink/linking/templates/shared/tfam_tables.sql +23 -0
- hlink/linking/templates/shared/training_features.sql +29 -0
- hlink/linking/templates/shared/training_prepped.sql +16 -0
- hlink/linking/training/__init__.py +6 -0
- hlink/linking/training/link_step_create_comparison_features.py +96 -0
- hlink/linking/training/link_step_ingest_file.py +27 -0
- hlink/linking/training/link_step_save_model_metadata.py +190 -0
- hlink/linking/training/link_step_train_and_save_model.py +80 -0
- hlink/linking/training/templates/.gitkeep +0 -0
- hlink/linking/training/training.py +26 -0
- hlink/linking/transformers/__init__.py +0 -0
- hlink/linking/transformers/float_cast_transformer.py +36 -0
- hlink/linking/transformers/rename_prob_column.py +14 -0
- hlink/linking/transformers/rename_vector_attributes.py +96 -0
- hlink/linking/util.py +18 -0
- hlink/scripts/__init__.py +0 -0
- hlink/scripts/lib/__init__.py +0 -0
- hlink/scripts/lib/conf_validations.py +409 -0
- hlink/scripts/lib/experimental/__init__.py +0 -0
- hlink/scripts/lib/experimental/reporting.py +112 -0
- hlink/scripts/lib/experimental/tfam.py +295 -0
- hlink/scripts/lib/io.py +91 -0
- hlink/scripts/lib/linking_ops.py +70 -0
- hlink/scripts/lib/table_ops.py +144 -0
- hlink/scripts/lib/util.py +31 -0
- hlink/scripts/main.py +230 -0
- hlink/scripts/main_loop.py +564 -0
- hlink/spark/__init__.py +0 -0
- hlink/spark/factory.py +91 -0
- hlink/spark/jars/hlink_lib-assembly-1.0.jar +0 -0
- hlink/spark/session.py +176 -0
- hlink/tests/__init__.py +0 -0
- hlink/tests/conf/duplicate_col_maps.toml +14 -0
- hlink/tests/conf/duplicate_comp_features.toml +36 -0
- hlink/tests/conf/duplicate_feature_sel.toml +24 -0
- hlink/tests/conf/integration.toml +70 -0
- hlink/tests/conf/missing_datasource_a.toml +1 -0
- hlink/tests/conf/missing_datasource_b.toml +4 -0
- hlink/tests/conf/no_id_column_a.toml +9 -0
- hlink/tests/conf/no_id_column_b.toml +9 -0
- hlink/tests/conf/test.json +7 -0
- hlink/tests/conf/test1.toml +4 -0
- hlink/tests/conf/test_conf_flag_run.json +7 -0
- hlink/tests/conf/test_run.json +7 -0
- hlink/tests/conf_validations_test.py +225 -0
- hlink/tests/config_loader_test.py +67 -0
- hlink/tests/conftest.py +1579 -0
- hlink/tests/core/__init__.py +0 -0
- hlink/tests/core/classifier_test.py +32 -0
- hlink/tests/core/column_mapping_test.py +308 -0
- hlink/tests/core/comparison_feature_test.py +456 -0
- hlink/tests/core/comparison_test.py +109 -0
- hlink/tests/core/model_metrics_test.py +200 -0
- hlink/tests/core/pipeline_test.py +37 -0
- hlink/tests/core/substitutions_test.py +63 -0
- hlink/tests/core/threshold_test.py +176 -0
- hlink/tests/core/transforms_test.py +413 -0
- hlink/tests/hh_matching_test.py +474 -0
- hlink/tests/hh_model_exploration_test.py +153 -0
- hlink/tests/hh_training_test.py +538 -0
- hlink/tests/input_data/19thc_nativity_test_hhs.csv +16 -0
- hlink/tests/input_data/19thc_nativity_test_hhs_a.csv +18 -0
- hlink/tests/input_data/19thc_nativity_test_hhs_b.csv +17 -0
- hlink/tests/input_data/birthyr_replace.csv +6 -0
- hlink/tests/input_data/calc_mfbpl_a.csv +16 -0
- hlink/tests/input_data/calc_mfbpl_b.csv +16 -0
- hlink/tests/input_data/conf_validation/a.csv +1 -0
- hlink/tests/input_data/conf_validation/b.csv +1 -0
- hlink/tests/input_data/conf_validation/empty.csv +0 -0
- hlink/tests/input_data/county_distances.csv +5 -0
- hlink/tests/input_data/crosswalk/hh_predicted_matches.csv +2 -0
- hlink/tests/input_data/crosswalk/predicted_matches.csv +6 -0
- hlink/tests/input_data/crosswalk/raw_df_a.csv +28 -0
- hlink/tests/input_data/crosswalk/raw_df_b.csv +28 -0
- hlink/tests/input_data/female.csv +4 -0
- hlink/tests/input_data/ha_source.csv +12 -0
- hlink/tests/input_data/handle_null.csv +10 -0
- hlink/tests/input_data/hb_source.csv +15 -0
- hlink/tests/input_data/hh_matching_a.csv +101 -0
- hlink/tests/input_data/hh_matching_b.csv +110 -0
- hlink/tests/input_data/hh_predicted_matches_reporting.csv +11 -0
- hlink/tests/input_data/hh_year_a.csv +7 -0
- hlink/tests/input_data/hh_year_b.csv +9 -0
- hlink/tests/input_data/hhpm_agg_test.csv +381 -0
- hlink/tests/input_data/households_b.parquet +0 -0
- hlink/tests/input_data/integration_a.csv +4 -0
- hlink/tests/input_data/integration_b.csv +4 -0
- hlink/tests/input_data/jw_blocking_test_a.csv +7 -0
- hlink/tests/input_data/jw_blocking_test_b.csv +2 -0
- hlink/tests/input_data/male.csv +2 -0
- hlink/tests/input_data/matched_men.csv +21 -0
- hlink/tests/input_data/matching_or_group_test_a.csv +59 -0
- hlink/tests/input_data/matching_or_group_test_b.csv +27 -0
- hlink/tests/input_data/matching_test_a.csv +59 -0
- hlink/tests/input_data/matching_test_b.csv +27 -0
- hlink/tests/input_data/nativity_test_data_a.csv +7 -0
- hlink/tests/input_data/nativity_test_data_b.csv +7 -0
- hlink/tests/input_data/new_hh_test_td.csv +199 -0
- hlink/tests/input_data/popularity.csv +9 -0
- hlink/tests/input_data/potential_matches.csv +25 -0
- hlink/tests/input_data/potential_matches_agg.csv +31 -0
- hlink/tests/input_data/potential_matches_ids_only.csv +10 -0
- hlink/tests/input_data/potential_matches_sql_condition_marst_warn.csv +37 -0
- hlink/tests/input_data/predicted_matches_reporting.csv +11 -0
- hlink/tests/input_data/predicted_matches_test.csv +6 -0
- hlink/tests/input_data/prepped_df_a_agg.csv +25 -0
- hlink/tests/input_data/prepped_df_b_agg.csv +31 -0
- hlink/tests/input_data/prepped_df_reporting.csv +28 -0
- hlink/tests/input_data/raw_df_reporting.csv +28 -0
- hlink/tests/input_data/region.csv +161 -0
- hlink/tests/input_data/regioncode.csv +52 -0
- hlink/tests/input_data/rel_rows_test_a.csv +28 -0
- hlink/tests/input_data/rel_rows_test_b.csv +34 -0
- hlink/tests/input_data/reporting_hh_predicted_matches.csv +10 -0
- hlink/tests/input_data/reporting_predicted_matches.csv +15 -0
- hlink/tests/input_data/reporting_prepped_df_a.csv +35 -0
- hlink/tests/input_data/representivity.csv +92 -0
- hlink/tests/input_data/scored_matches_household_test.csv +7 -0
- hlink/tests/input_data/scored_matches_test_data.csv +7 -0
- hlink/tests/input_data/sql_condition_marst_warn_a.csv +37 -0
- hlink/tests/input_data/sql_condition_marst_warn_b.csv +37 -0
- hlink/tests/input_data/statedist.csv +2602 -0
- hlink/tests/input_data/street_abbrevs.csv +544 -0
- hlink/tests/input_data/street_abbrevs_most_common.csv +72 -0
- hlink/tests/input_data/test_csv_data_a.csv +4 -0
- hlink/tests/input_data/test_csv_data_b.csv +4 -0
- hlink/tests/input_data/test_parquet_data_a.parquet/._SUCCESS.crc +0 -0
- hlink/tests/input_data/test_parquet_data_a.parquet/.part-00000-8465f06e-9bb0-4817-81eb-813ccf07429a-c000.snappy.parquet.crc +0 -0
- hlink/tests/input_data/test_parquet_data_a.parquet/_SUCCESS +0 -0
- hlink/tests/input_data/test_parquet_data_a.parquet/part-00000-8465f06e-9bb0-4817-81eb-813ccf07429a-c000.snappy.parquet +0 -0
- hlink/tests/input_data/test_parquet_data_b.parquet/._SUCCESS.crc +0 -0
- hlink/tests/input_data/test_parquet_data_b.parquet/.part-00000-bb515275-04b4-4a16-80f1-5cc21450b93f-c000.snappy.parquet.crc +0 -0
- hlink/tests/input_data/test_parquet_data_b.parquet/_SUCCESS +0 -0
- hlink/tests/input_data/test_parquet_data_b.parquet/part-00000-bb515275-04b4-4a16-80f1-5cc21450b93f-c000.snappy.parquet +0 -0
- hlink/tests/input_data/test_prepped_data_a.csv +4 -0
- hlink/tests/input_data/test_prepped_data_b.csv +4 -0
- hlink/tests/input_data/test_street_names_data.csv +10 -0
- hlink/tests/input_data/threshold_ratio_test.csv +17 -0
- hlink/tests/input_data/threshold_ratio_test_data_2.csv +14 -0
- hlink/tests/input_data/training_data.csv +10 -0
- hlink/tests/input_data/training_data_doubled.csv +19 -0
- hlink/tests/input_data/training_data_households.csv +59 -0
- hlink/tests/input_data/training_data_households.parquet +0 -0
- hlink/tests/input_data/training_data_long.csv +25 -0
- hlink/tests/input_data/training_data_long_a.csv +25 -0
- hlink/tests/input_data/training_data_long_b.csv +25 -0
- hlink/tests/integration_score_with_trained_models_test.py +1417 -0
- hlink/tests/integration_test.py +51 -0
- hlink/tests/linking_util_test.py +12 -0
- hlink/tests/logging_test.py +14 -0
- hlink/tests/main_loop_test.py +147 -0
- hlink/tests/markers.py +31 -0
- hlink/tests/matching_blocking_explode_test.py +284 -0
- hlink/tests/matching_comparison_features_test.py +1197 -0
- hlink/tests/matching_geo_distance_test.py +427 -0
- hlink/tests/matching_potential_matches_test.py +73 -0
- hlink/tests/matching_potential_matches_universe_test.py +70 -0
- hlink/tests/matching_scoring_test.py +174 -0
- hlink/tests/matching_test.py +215 -0
- hlink/tests/model_exploration_test.py +1016 -0
- hlink/tests/plugins/__init__.py +0 -0
- hlink/tests/plugins/datasources.py +1037 -0
- hlink/tests/plugins/external_data_paths.py +409 -0
- hlink/tests/preprocessing_test.py +1884 -0
- hlink/tests/reporting_test.py +117 -0
- hlink/tests/scala_udf_test.py +29 -0
- hlink/tests/spark_connection_test.py +56 -0
- hlink/tests/spark_factory_test.py +52 -0
- hlink/tests/table_test.py +65 -0
- hlink/tests/training_test.py +718 -0
- hlink/tests/transformers_test.py +77 -0
- hlink/tests/validation_data/crosswalks/crosswalk.csv +7 -0
- hlink/tests/validation_data/crosswalks/crosswalk_with_round.csv +7 -0
- hlink/tests/validation_data/training_p_all.parquet/._SUCCESS.crc +0 -0
- hlink/tests/validation_data/training_p_all.parquet/.part-00000-6c9e5760-174a-4652-b687-6823087ed5bf-c000.snappy.parquet.crc +0 -0
- hlink/tests/validation_data/training_p_all.parquet/_SUCCESS +0 -0
- hlink/tests/validation_data/training_p_all.parquet/part-00000-6c9e5760-174a-4652-b687-6823087ed5bf-c000.snappy.parquet +0 -0
- hlink-4.1.0.dist-info/METADATA +646 -0
- hlink-4.1.0.dist-info/RECORD +260 -0
- hlink-4.1.0.dist-info/WHEEL +5 -0
- hlink-4.1.0.dist-info/entry_points.txt +2 -0
- hlink-4.1.0.dist-info/licenses/LICENSE.txt +373 -0
- hlink-4.1.0.dist-info/licenses/NOTICE.txt +16 -0
- 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."""
|
hlink/linking/README.md
ADDED
|
@@ -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 ""
|