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.
- pseudopeople/__about__.py +19 -0
- pseudopeople/__init__.py +21 -0
- pseudopeople/_version.py +1 -0
- pseudopeople/column_getters.py +7 -0
- pseudopeople/configuration/__init__.py +2 -0
- pseudopeople/configuration/entities.py +17 -0
- pseudopeople/configuration/generator.py +236 -0
- pseudopeople/configuration/interface.py +51 -0
- pseudopeople/configuration/validator.py +345 -0
- pseudopeople/constants/data_values.py +45 -0
- pseudopeople/constants/metadata.py +86 -0
- pseudopeople/constants/noise_type_metadata.py +42 -0
- pseudopeople/constants/paths.py +16 -0
- pseudopeople/data/fake_names.py +190 -0
- pseudopeople/data/incorrect_select_options.csv +52 -0
- pseudopeople/data/nicknames.csv +1081 -0
- pseudopeople/data/ocr_errors.csv +51 -0
- pseudopeople/data/phonetic_variations.csv +358 -0
- pseudopeople/data/qwerty_errors.yaml +382 -0
- pseudopeople/data/sample_datasets/american_community_survey/american_community_survey.parquet +0 -0
- pseudopeople/data/sample_datasets/current_population_survey/current_population_survey.parquet +0 -0
- pseudopeople/data/sample_datasets/decennial_census/decennial_census.parquet +0 -0
- pseudopeople/data/sample_datasets/metadata_proportions.csv +38769 -0
- pseudopeople/data/sample_datasets/social_security/social_security.parquet +0 -0
- pseudopeople/data/sample_datasets/taxes_1040/taxes_1040.parquet +0 -0
- pseudopeople/data/sample_datasets/taxes_w2_and_1099/taxes_w2_and_1099.parquet +0 -0
- pseudopeople/data/sample_datasets/women_infants_and_children/women_infants_and_children.parquet +0 -0
- pseudopeople/dtypes.py +7 -0
- pseudopeople/entity_types.py +137 -0
- pseudopeople/exceptions.py +15 -0
- pseudopeople/interface.py +1017 -0
- pseudopeople/loader.py +51 -0
- pseudopeople/noise.py +120 -0
- pseudopeople/noise_entities.py +117 -0
- pseudopeople/noise_functions.py +929 -0
- pseudopeople/noise_scaling.py +82 -0
- pseudopeople/output_dtype_getters.py +15 -0
- pseudopeople/schema_entities.py +823 -0
- pseudopeople/utilities.py +334 -0
- pseudopeople-1.2.7.dist-info/METADATA +90 -0
- pseudopeople-1.2.7.dist-info/RECORD +44 -0
- pseudopeople-1.2.7.dist-info/WHEEL +5 -0
- pseudopeople-1.2.7.dist-info/licenses/LICENSE +28 -0
- 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__}"
|
pseudopeople/__init__.py
ADDED
|
@@ -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
|
+
)
|
pseudopeople/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.7"
|
|
@@ -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()
|