pseudopeople 1.2.7__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 (44) hide show
  1. pseudopeople/__about__.py +19 -0
  2. pseudopeople/__init__.py +21 -0
  3. pseudopeople/_version.py +1 -0
  4. pseudopeople/column_getters.py +7 -0
  5. pseudopeople/configuration/__init__.py +2 -0
  6. pseudopeople/configuration/entities.py +17 -0
  7. pseudopeople/configuration/generator.py +236 -0
  8. pseudopeople/configuration/interface.py +51 -0
  9. pseudopeople/configuration/validator.py +345 -0
  10. pseudopeople/constants/data_values.py +45 -0
  11. pseudopeople/constants/metadata.py +86 -0
  12. pseudopeople/constants/noise_type_metadata.py +42 -0
  13. pseudopeople/constants/paths.py +16 -0
  14. pseudopeople/data/fake_names.py +190 -0
  15. pseudopeople/data/incorrect_select_options.csv +52 -0
  16. pseudopeople/data/nicknames.csv +1081 -0
  17. pseudopeople/data/ocr_errors.csv +51 -0
  18. pseudopeople/data/phonetic_variations.csv +358 -0
  19. pseudopeople/data/qwerty_errors.yaml +382 -0
  20. pseudopeople/data/sample_datasets/american_community_survey/american_community_survey.parquet +0 -0
  21. pseudopeople/data/sample_datasets/current_population_survey/current_population_survey.parquet +0 -0
  22. pseudopeople/data/sample_datasets/decennial_census/decennial_census.parquet +0 -0
  23. pseudopeople/data/sample_datasets/metadata_proportions.csv +38769 -0
  24. pseudopeople/data/sample_datasets/social_security/social_security.parquet +0 -0
  25. pseudopeople/data/sample_datasets/taxes_1040/taxes_1040.parquet +0 -0
  26. pseudopeople/data/sample_datasets/taxes_w2_and_1099/taxes_w2_and_1099.parquet +0 -0
  27. pseudopeople/data/sample_datasets/women_infants_and_children/women_infants_and_children.parquet +0 -0
  28. pseudopeople/dtypes.py +7 -0
  29. pseudopeople/entity_types.py +137 -0
  30. pseudopeople/exceptions.py +15 -0
  31. pseudopeople/interface.py +1017 -0
  32. pseudopeople/loader.py +51 -0
  33. pseudopeople/noise.py +120 -0
  34. pseudopeople/noise_entities.py +117 -0
  35. pseudopeople/noise_functions.py +929 -0
  36. pseudopeople/noise_scaling.py +82 -0
  37. pseudopeople/output_dtype_getters.py +15 -0
  38. pseudopeople/schema_entities.py +823 -0
  39. pseudopeople/utilities.py +334 -0
  40. pseudopeople-1.2.7.dist-info/METADATA +90 -0
  41. pseudopeople-1.2.7.dist-info/RECORD +44 -0
  42. pseudopeople-1.2.7.dist-info/WHEEL +5 -0
  43. pseudopeople-1.2.7.dist-info/licenses/LICENSE +28 -0
  44. pseudopeople-1.2.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,19 @@
1
+ __all__ = [
2
+ "__title__",
3
+ "__summary__",
4
+ "__uri__",
5
+ "__author__",
6
+ "__email__",
7
+ "__license__",
8
+ "__copyright__",
9
+ ]
10
+
11
+ __title__ = "pseudopeople"
12
+ __summary__ = "pseudopeople generates realistic simulated data about a fictional United States population, designed for use in testing entity resolution (record linkage) methods or other data science algorithms at scale."
13
+ __uri__ = "https://github.com/ihmeuw/pseudopeople"
14
+
15
+ __author__ = "The pseudopeople developers"
16
+ __email__ = "vivarium.dev@gmail.com"
17
+
18
+ __license__ = "BSD-3-Clause"
19
+ __copyright__ = f"Copyright 2023 {__author__}"
@@ -0,0 +1,21 @@
1
+ from pseudopeople.__about__ import (
2
+ __author__,
3
+ __copyright__,
4
+ __email__,
5
+ __license__,
6
+ __summary__,
7
+ __title__,
8
+ __uri__,
9
+ )
10
+ from pseudopeople._version import __version__
11
+ from pseudopeople.configuration.entities import NO_NOISE
12
+ from pseudopeople.configuration.interface import get_config
13
+ from pseudopeople.interface import (
14
+ generate_american_community_survey,
15
+ generate_current_population_survey,
16
+ generate_decennial_census,
17
+ generate_social_security,
18
+ generate_taxes_1040,
19
+ generate_taxes_w2_and_1099,
20
+ generate_women_infants_and_children,
21
+ )
@@ -0,0 +1 @@
1
+ __version__ = "1.2.7"
@@ -0,0 +1,7 @@
1
+ from typing import List
2
+
3
+ from pseudopeople.constants.noise_type_metadata import COPY_HOUSEHOLD_MEMBER_COLS
4
+
5
+
6
+ def copy_from_household_member_column_getter(column_name) -> List[str]:
7
+ return [COPY_HOUSEHOLD_MEMBER_COLS[column_name]]
@@ -0,0 +1,2 @@
1
+ from pseudopeople.configuration.entities import NO_NOISE, Keys
2
+ from pseudopeople.configuration.generator import get_configuration
@@ -0,0 +1,17 @@
1
+ class Keys:
2
+ """Container for all non-dataset standard/repeated key names used in the configuration file"""
3
+
4
+ ROW_NOISE = "row_noise" # second layer, eg <dataset>: row_noise: {...}
5
+ COLUMN_NOISE = "column_noise" # second layer, eg <dataset>: column_noise: {...}
6
+ ROW_PROBABILITY = "row_probability"
7
+ CELL_PROBABILITY = "cell_probability"
8
+ TOKEN_PROBABILITY = "token_probability"
9
+ POSSIBLE_AGE_DIFFERENCES = "possible_age_differences"
10
+ ZIPCODE_DIGIT_PROBABILITIES = "digit_probabilities"
11
+ ROW_PROBABILITY_IN_HOUSEHOLDS_UNDER_18 = "row_probability_in_households_under_18"
12
+ ROW_PROBABILITY_IN_COLLEGE_GROUP_QUARTERS_UNDER_24 = (
13
+ "row_probability_in_college_group_quarters_under_24"
14
+ )
15
+
16
+
17
+ NO_NOISE = "no_noise"
@@ -0,0 +1,236 @@
1
+ from pathlib import Path
2
+ from typing import Dict, List, Optional, Tuple, Union
3
+
4
+ import pandas as pd
5
+ import yaml
6
+ from layered_config_tree import LayeredConfigTree
7
+
8
+ from pseudopeople.configuration import NO_NOISE, Keys
9
+ from pseudopeople.configuration.validator import (
10
+ validate_noise_level_proportions,
11
+ validate_overrides,
12
+ )
13
+ from pseudopeople.constants.data_values import DEFAULT_DO_NOT_RESPOND_ROW_PROBABILITY
14
+ from pseudopeople.entity_types import RowNoiseType
15
+ from pseudopeople.noise_entities import NOISE_TYPES
16
+ from pseudopeople.schema_entities import COLUMNS, DATASETS, Dataset
17
+
18
+ # Define non-baseline default items
19
+ # NOTE: default values are defined in entity_types.RowNoiseType and entity_types.ColumnNoiseType
20
+ DEFAULT_NOISE_VALUES = {
21
+ DATASETS.census.name: {
22
+ Keys.ROW_NOISE: {
23
+ NOISE_TYPES.do_not_respond.name: {
24
+ Keys.ROW_PROBABILITY: DEFAULT_DO_NOT_RESPOND_ROW_PROBABILITY[
25
+ DATASETS.census.name
26
+ ],
27
+ }
28
+ },
29
+ },
30
+ DATASETS.acs.name: {
31
+ Keys.ROW_NOISE: {
32
+ NOISE_TYPES.do_not_respond.name: {
33
+ Keys.ROW_PROBABILITY: DEFAULT_DO_NOT_RESPOND_ROW_PROBABILITY[
34
+ DATASETS.acs.name
35
+ ],
36
+ },
37
+ },
38
+ },
39
+ DATASETS.cps.name: {
40
+ Keys.ROW_NOISE: {
41
+ NOISE_TYPES.do_not_respond.name: {
42
+ Keys.ROW_PROBABILITY: DEFAULT_DO_NOT_RESPOND_ROW_PROBABILITY[
43
+ DATASETS.cps.name
44
+ ],
45
+ },
46
+ },
47
+ },
48
+ DATASETS.tax_w2_1099.name: {
49
+ Keys.ROW_NOISE: {
50
+ NOISE_TYPES.omit_row.name: {
51
+ Keys.ROW_PROBABILITY: 0.005,
52
+ },
53
+ },
54
+ Keys.COLUMN_NOISE: {
55
+ COLUMNS.ssn.name: {
56
+ NOISE_TYPES.copy_from_household_member.name: {
57
+ Keys.CELL_PROBABILITY: 0.00,
58
+ }
59
+ },
60
+ },
61
+ },
62
+ DATASETS.wic.name: {
63
+ Keys.ROW_NOISE: {
64
+ NOISE_TYPES.omit_row.name: {
65
+ Keys.ROW_PROBABILITY: 0.005,
66
+ },
67
+ },
68
+ },
69
+ # No noise of any kind for SSN in the SSA observer
70
+ DATASETS.ssa.name: {
71
+ Keys.COLUMN_NOISE: {
72
+ COLUMNS.ssn.name: {
73
+ noise_type.name: {
74
+ Keys.CELL_PROBABILITY: 0.0,
75
+ }
76
+ for noise_type in COLUMNS.ssn.noise_types
77
+ },
78
+ },
79
+ },
80
+ }
81
+
82
+
83
+ def get_configuration(
84
+ overrides: Optional[Union[Path, str, Dict]] = None,
85
+ dataset: Dataset = None,
86
+ user_filters: List[Tuple[Union[str, int, pd.Timestamp]]] = None,
87
+ ) -> LayeredConfigTree:
88
+ """
89
+ Gets a noising configuration LayeredConfigTree, optionally overridden by a user-provided YAML.
90
+
91
+ :param overrides: A path to the YAML file or a dictionary defining user overrides for the defaults
92
+ :return: a LayeredConfigTree object of the noising configuration
93
+ """
94
+
95
+ if overrides == NO_NOISE:
96
+ is_no_noise = True
97
+ overrides = None
98
+ elif isinstance(overrides, (Path, str)):
99
+ with open(overrides, "r") as f:
100
+ overrides = yaml.safe_load(f)
101
+ is_no_noise = False
102
+ else:
103
+ is_no_noise = False
104
+ noising_configuration = _generate_configuration(is_no_noise)
105
+ if overrides is not None:
106
+ add_overrides(noising_configuration, overrides, dataset, user_filters)
107
+
108
+ return noising_configuration
109
+
110
+
111
+ def _generate_configuration(is_no_noise: bool) -> LayeredConfigTree:
112
+ default_config_layers = [
113
+ "baseline",
114
+ "default",
115
+ "user",
116
+ ]
117
+ noising_configuration = LayeredConfigTree(layers=default_config_layers)
118
+ # Instantiate the configuration file with baseline values
119
+ baseline_dict = {}
120
+ # Loop through each dataset
121
+ for dataset in DATASETS:
122
+ dataset_dict = {}
123
+ row_noise_dict = {}
124
+ column_dict = {}
125
+
126
+ # Loop through row noise types
127
+ for row_noise in dataset.row_noise_types:
128
+ row_noise_type_dict = get_noise_type_dict(row_noise, is_no_noise)
129
+ if row_noise_type_dict:
130
+ row_noise_dict[row_noise.name] = row_noise_type_dict
131
+
132
+ # Loop through columns and their applicable column noise types
133
+ for column in dataset.columns:
134
+ column_noise_dict = {}
135
+ for noise_type in column.noise_types:
136
+ column_noise_type_dict = get_noise_type_dict(noise_type, is_no_noise)
137
+ if column_noise_type_dict:
138
+ column_noise_dict[noise_type.name] = column_noise_type_dict
139
+ if column_noise_dict:
140
+ column_dict[column.name] = column_noise_dict
141
+
142
+ # Compile
143
+ if row_noise_dict:
144
+ dataset_dict[Keys.ROW_NOISE] = row_noise_dict
145
+ if column_dict:
146
+ dataset_dict[Keys.COLUMN_NOISE] = column_dict
147
+
148
+ # Add the dataset's dictionary to baseline
149
+ if dataset_dict:
150
+ baseline_dict[dataset.name] = dataset_dict
151
+
152
+ noising_configuration.update(baseline_dict, layer="baseline")
153
+
154
+ # Update configuration with non-baseline default values
155
+ if not is_no_noise:
156
+ noising_configuration.update(DEFAULT_NOISE_VALUES, layer="default")
157
+ return noising_configuration
158
+
159
+
160
+ def get_noise_type_dict(noise_type, is_no_noise: bool) -> Dict:
161
+ noise_type_dict = {}
162
+ if noise_type.probability is not None:
163
+ noise_level = 0.0 if is_no_noise else noise_type.probability
164
+ noise_type_dict[noise_type.probability_key] = noise_level
165
+ if noise_type.additional_parameters is not None:
166
+ for key, value in noise_type.additional_parameters.items():
167
+ # FIXME: This makes a big assumption that the additional parameters are all floats
168
+ # If we were to add a noise type or additional parameter key that was a list or dict
169
+ # like we have in some column noise types this would not work.
170
+ noise_level = (
171
+ 0.0 if is_no_noise and isinstance(noise_type, RowNoiseType) else value
172
+ )
173
+ noise_type_dict[key] = noise_level
174
+ return noise_type_dict
175
+
176
+
177
+ def add_overrides(
178
+ noising_configuration: LayeredConfigTree,
179
+ overrides: Dict,
180
+ dataset: Dataset = None,
181
+ user_filters: List[Tuple[Union[str, int, pd.Timestamp]]] = None,
182
+ ) -> None:
183
+ validate_overrides(overrides, noising_configuration)
184
+ overrides = _format_overrides(noising_configuration, overrides)
185
+ noising_configuration.update(overrides, layer="user")
186
+ # Note: dataset and user_filters should both be None when using the get_config wrapper
187
+ # or both be inputs from generate_XXX functions.
188
+ if (dataset is not None) and (user_filters is not None):
189
+ # TODO: refactor validate_noise_level_proportions to take overrides as arg and live in validate overrides
190
+ # Note: validate_noise_level_proportions must happen after user layer configuration update
191
+ validate_noise_level_proportions(noising_configuration, dataset, user_filters)
192
+
193
+
194
+ def _format_overrides(default_config: LayeredConfigTree, user_dict: Dict) -> Dict:
195
+ """Formats the user's configuration file as necessary, so it can properly
196
+ update noising configuration to be used
197
+ """
198
+ user_dict = _format_misreport_age_perturbations(default_config, user_dict)
199
+ return user_dict
200
+
201
+
202
+ def _format_misreport_age_perturbations(
203
+ default_config: LayeredConfigTree, user_dict: Dict
204
+ ) -> Dict:
205
+ # Format any age perturbation lists as a dictionary with uniform probabilities
206
+ for dataset in user_dict:
207
+ user_perturbations = (
208
+ user_dict[dataset]
209
+ .get(Keys.COLUMN_NOISE, {})
210
+ .get("age", {})
211
+ .get(NOISE_TYPES.misreport_age.name, {})
212
+ .get(Keys.POSSIBLE_AGE_DIFFERENCES, {})
213
+ )
214
+ if not user_perturbations:
215
+ continue
216
+ formatted = {}
217
+ default_perturbations = default_config[dataset][Keys.COLUMN_NOISE]["age"][
218
+ NOISE_TYPES.misreport_age.name
219
+ ][Keys.POSSIBLE_AGE_DIFFERENCES]
220
+ # Replace default configuration with 0 probabilities
221
+ for perturbation in default_perturbations:
222
+ formatted[perturbation] = 0
223
+ if isinstance(user_perturbations, list):
224
+ # Add user perturbations with uniform probabilities
225
+ uniform_prob = 1 / len(user_perturbations)
226
+ for perturbation in user_perturbations:
227
+ formatted[perturbation] = uniform_prob
228
+ else:
229
+ for perturbation, prob in user_perturbations.items():
230
+ formatted[perturbation] = prob
231
+
232
+ user_dict[dataset][Keys.COLUMN_NOISE]["age"][NOISE_TYPES.misreport_age.name][
233
+ Keys.POSSIBLE_AGE_DIFFERENCES
234
+ ] = formatted
235
+
236
+ return user_dict
@@ -0,0 +1,51 @@
1
+ from pathlib import Path
2
+ from typing import Dict, Union
3
+
4
+ from pseudopeople.configuration.generator import get_configuration
5
+
6
+
7
+ def get_config(overrides: Union[Path, str, Dict] = None) -> Dict:
8
+ """
9
+ Function that returns the pseudopeople configuration containing all
10
+ default values. To get the default probability of nonresponse in the
11
+ Decennial Census dataset:
12
+
13
+ .. code-block:: pycon
14
+
15
+ >>> import pseudopeople as psp
16
+ >>> psp.get_config()['decennial_census']['row_noise']['do_not_respond']
17
+ {'row_probability': 0.0145}
18
+
19
+ To view that same part of the configuration after applying a user
20
+ override:
21
+
22
+ .. code-block:: pycon
23
+
24
+ >>> overrides = {'decennial_census': {'row_noise': {'do_not_respond': {'row_probability': 0.1}}}}
25
+ >>> psp.get_config(overrides)['decenial_census']['row_noise']['do_not_respond']
26
+ {'row_probability': 0.1}
27
+
28
+ :param overrides:
29
+
30
+ An optional set of overrides to the default configuration. Can
31
+ be a (nested) Python dictionary mapping noise type parameters to
32
+ the desired override values, a path to a YAML file with the same
33
+ nested structure (see the :ref:`configuration structure
34
+ <configuration_structure>` section of the documentation for
35
+ details), or the special sentinel value `pseudopeople.NO_NOISE`,
36
+ which will return a configuration in which all configurable
37
+ noise is set to zero. When passing a dictionary or YAML file, it
38
+ is not necessary to provide a complete configuration; any
39
+ configuration parameters not specified in `overrides` will be
40
+ filled in with the default values.
41
+
42
+ :return:
43
+
44
+ A complete configuration dictionary.
45
+
46
+ :raises ConfigurationError:
47
+
48
+ An invalid configuration is passed with `overrides`.
49
+
50
+ """
51
+ return get_configuration(overrides).to_dict()