rdt 1.9.1.dev0__tar.gz → 1.9.2.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/HISTORY.md +8 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/PKG-INFO +9 -1
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/__init__.py +1 -1
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/hyper_transformer.py +31 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/base.py +4 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/categorical.py +19 -5
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/numerical.py +2 -31
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/text.py +60 -35
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/utils.py +92 -1
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/PKG-INFO +9 -1
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/setup.cfg +1 -1
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/setup.py +1 -1
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/test_hyper_transformer.py +103 -1
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_base.py +2 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_categorical.py +30 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_text.py +34 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/test_hyper_transformer.py +121 -10
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_base.py +6 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_categorical.py +78 -5
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_numerical.py +0 -80
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_text.py +87 -33
- rdt-1.9.2.dev0/tests/unit/transformers/test_utils.py +215 -0
- rdt-1.9.1.dev0/tests/unit/transformers/test_utils.py +0 -68
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/AUTHORS.rst +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/CONTRIBUTING.rst +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/LICENSE +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/MANIFEST.in +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/README.md +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/RELEASE.md +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/errors.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/base.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/boolean.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/categorical.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/datetime.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/numerical.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/pii.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/text.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/utils.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/performance.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/profiling.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/addons/addons_setup.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/boolean.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/datetime.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/null.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/pii/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/pii/anonymization.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/pii/anonymizer.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/pii/utils.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/SOURCES.txt +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/dependency_links.txt +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/not-zip-safe +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/requires.txt +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/top_level.txt +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/code_style.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/contributing.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_boolean.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_categorical.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_datetime.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_numerical.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_utils.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/test_transformers.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/pii/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/pii/test_anonymization.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/pii/test_anonymizer.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_boolean.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_datetime.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_numerical.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/README.md +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/test_performance.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/tests/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/tests/test_profiling.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/test___init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/addons/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/addons/identity/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/addons/identity/test_identity.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/pii/__init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/pii/test_anonymization.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/pii/test_anonymizer.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/pii/test_utils.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test___init__.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_boolean.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_datetime.py +0 -0
- {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_null.py +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# History
|
|
2
2
|
|
|
3
|
+
## 1.9.1 - 2024-01-10
|
|
4
|
+
|
|
5
|
+
This release fixes a bug that caused the `AnonymizedFaker` to crash with provider/function combinations that return tuples.
|
|
6
|
+
|
|
7
|
+
### Bugs Fixed
|
|
8
|
+
|
|
9
|
+
* AnonymizedFaker crashes with ValueError for specific provider/function pairs (eg. currency) - Issue [#743](https://github.com/sdv-dev/RDT/issues/743) by @ R-Palazzo
|
|
10
|
+
|
|
3
11
|
## 1.9.0 - 2023-11-14
|
|
4
12
|
|
|
5
13
|
This release adds a parameter to the `UnixTimestampEncoder` and `OptimizedTimestampEncoder`, called `enforce_min_max_values`. When this is set to True, it clips all values in the reverse transformed data to the min and max datetimes seen in the fitted data.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rdt
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.2.dev0
|
|
4
4
|
Summary: Reversible Data Transforms
|
|
5
5
|
Home-page: https://github.com/sdv-dev/RDT
|
|
6
6
|
Author: DataCebo, Inc.
|
|
@@ -225,6 +225,14 @@ for specific needs.
|
|
|
225
225
|
|
|
226
226
|
# History
|
|
227
227
|
|
|
228
|
+
## 1.9.1 - 2024-01-10
|
|
229
|
+
|
|
230
|
+
This release fixes a bug that caused the `AnonymizedFaker` to crash with provider/function combinations that return tuples.
|
|
231
|
+
|
|
232
|
+
### Bugs Fixed
|
|
233
|
+
|
|
234
|
+
* AnonymizedFaker crashes with ValueError for specific provider/function pairs (eg. currency) - Issue [#743](https://github.com/sdv-dev/RDT/issues/743) by @ R-Palazzo
|
|
235
|
+
|
|
228
236
|
## 1.9.0 - 2023-11-14
|
|
229
237
|
|
|
230
238
|
This release adds a parameter to the `UnixTimestampEncoder` and `OptimizedTimestampEncoder`, called `enforce_min_max_values`. When this is set to True, it clips all values in the reverse transformed data to the min and max datetimes seen in the fitted data.
|
|
@@ -357,6 +357,32 @@ class HyperTransformer:
|
|
|
357
357
|
|
|
358
358
|
self.field_transformers[new_tuple] = self.field_transformers.pop(old_tuple)
|
|
359
359
|
|
|
360
|
+
def _update_multi_column_transformer(self):
|
|
361
|
+
"""Check that multi-columns mappings are valid and update them otherwise."""
|
|
362
|
+
all_fields_multi_column = set()
|
|
363
|
+
for columns, transformer in self.field_transformers.items():
|
|
364
|
+
if isinstance(transformer, BaseMultiColumnTransformer):
|
|
365
|
+
all_fields_multi_column.add(columns)
|
|
366
|
+
|
|
367
|
+
for field in all_fields_multi_column:
|
|
368
|
+
transformer = self.field_transformers[field]
|
|
369
|
+
|
|
370
|
+
columns_to_sdtypes = self._get_columns_to_sdtypes(field)
|
|
371
|
+
try:
|
|
372
|
+
transformer._validate_sdtypes( # pylint: disable=protected-access
|
|
373
|
+
columns_to_sdtypes
|
|
374
|
+
)
|
|
375
|
+
except TransformerInputError:
|
|
376
|
+
warnings.warn(
|
|
377
|
+
f"Transformer '{transformer.get_name()}' is incompatible with the "
|
|
378
|
+
f"multi-column field '{field}'. Assigning default transformer to the columns."
|
|
379
|
+
)
|
|
380
|
+
del self.field_transformers[field]
|
|
381
|
+
for column, sdtype in columns_to_sdtypes.items():
|
|
382
|
+
self.field_transformers[column] = deepcopy(get_default_transformer(sdtype))
|
|
383
|
+
|
|
384
|
+
self._multi_column_fields = self._create_multi_column_fields()
|
|
385
|
+
|
|
360
386
|
def update_transformers_by_sdtype(
|
|
361
387
|
self, sdtype, transformer=None, transformer_name=None, transformer_parameters=None):
|
|
362
388
|
"""Update the transformers for the specified ``sdtype``.
|
|
@@ -397,6 +423,7 @@ class HyperTransformer:
|
|
|
397
423
|
self._remove_column_in_multi_column_fields(field)
|
|
398
424
|
|
|
399
425
|
self._multi_column_fields = self._create_multi_column_fields()
|
|
426
|
+
self._update_multi_column_transformer()
|
|
400
427
|
self._modified_config = True
|
|
401
428
|
|
|
402
429
|
def update_sdtypes(self, column_name_to_sdtype):
|
|
@@ -445,6 +472,7 @@ class HyperTransformer:
|
|
|
445
472
|
)
|
|
446
473
|
|
|
447
474
|
self._multi_column_fields = self._create_multi_column_fields()
|
|
475
|
+
self._update_multi_column_transformer()
|
|
448
476
|
self._modified_config = True
|
|
449
477
|
if self._fitted:
|
|
450
478
|
warnings.warn(self._REFIT_MESSAGE)
|
|
@@ -485,6 +513,7 @@ class HyperTransformer:
|
|
|
485
513
|
self.field_transformers[column_name] = transformer
|
|
486
514
|
|
|
487
515
|
self._multi_column_fields = self._create_multi_column_fields()
|
|
516
|
+
self._update_multi_column_transformer()
|
|
488
517
|
self._modified_config = True
|
|
489
518
|
|
|
490
519
|
def remove_transformers(self, column_names):
|
|
@@ -514,6 +543,7 @@ class HyperTransformer:
|
|
|
514
543
|
|
|
515
544
|
self.field_transformers[column_name] = None
|
|
516
545
|
|
|
546
|
+
self._update_multi_column_transformer()
|
|
517
547
|
if self._fitted:
|
|
518
548
|
warnings.warn(self._REFIT_MESSAGE)
|
|
519
549
|
|
|
@@ -540,6 +570,7 @@ class HyperTransformer:
|
|
|
540
570
|
|
|
541
571
|
self.field_transformers[column_name] = None
|
|
542
572
|
|
|
573
|
+
self._update_multi_column_transformer()
|
|
543
574
|
if self._fitted:
|
|
544
575
|
warnings.warn(self._REFIT_MESSAGE)
|
|
545
576
|
|
|
@@ -547,6 +547,10 @@ class BaseMultiColumnTransformer(BaseTransformer):
|
|
|
547
547
|
missing_to_print = ', '.join(missing)
|
|
548
548
|
raise ValueError(f'Columns ({missing_to_print}) are not present in the data.')
|
|
549
549
|
|
|
550
|
+
@classmethod
|
|
551
|
+
def _validate_sdtypes(cls, columns_to_sdtypes):
|
|
552
|
+
raise NotImplementedError()
|
|
553
|
+
|
|
550
554
|
def _fit(self, data):
|
|
551
555
|
"""Fit the transformer to the data.
|
|
552
556
|
|
|
@@ -9,7 +9,7 @@ from scipy.stats import norm
|
|
|
9
9
|
|
|
10
10
|
from rdt.errors import TransformerInputError
|
|
11
11
|
from rdt.transformers.base import BaseTransformer
|
|
12
|
-
from rdt.transformers.utils import fill_nan_with_none
|
|
12
|
+
from rdt.transformers.utils import check_nan_in_transform, fill_nan_with_none, try_convert_to_dtype
|
|
13
13
|
|
|
14
14
|
LOGGER = logging.getLogger(__name__)
|
|
15
15
|
|
|
@@ -177,6 +177,7 @@ class UniformEncoder(BaseTransformer):
|
|
|
177
177
|
Returns:
|
|
178
178
|
pandas.Series
|
|
179
179
|
"""
|
|
180
|
+
check_nan_in_transform(data, self.dtype)
|
|
180
181
|
data = data.clip(0, 1)
|
|
181
182
|
bins = [0]
|
|
182
183
|
labels = []
|
|
@@ -192,7 +193,10 @@ class UniformEncoder(BaseTransformer):
|
|
|
192
193
|
labels.append(key)
|
|
193
194
|
|
|
194
195
|
result = pd.cut(data, bins=bins, labels=labels, include_lowest=True)
|
|
195
|
-
|
|
196
|
+
result = result.replace(nan_name, np.nan)
|
|
197
|
+
result = try_convert_to_dtype(result, self.dtype)
|
|
198
|
+
|
|
199
|
+
return result
|
|
196
200
|
|
|
197
201
|
|
|
198
202
|
class OrderedUniformEncoder(UniformEncoder):
|
|
@@ -333,6 +337,7 @@ class FrequencyEncoder(BaseTransformer):
|
|
|
333
337
|
)
|
|
334
338
|
super().__init__()
|
|
335
339
|
self.add_noise = add_noise
|
|
340
|
+
self._is_integer = None
|
|
336
341
|
|
|
337
342
|
@staticmethod
|
|
338
343
|
def _get_intervals(data):
|
|
@@ -516,6 +521,7 @@ class FrequencyEncoder(BaseTransformer):
|
|
|
516
521
|
Returns:
|
|
517
522
|
pandas.Series
|
|
518
523
|
"""
|
|
524
|
+
check_nan_in_transform(data, self.dtype)
|
|
519
525
|
data = data.clip(0, 1)
|
|
520
526
|
num_rows = len(data)
|
|
521
527
|
num_categories = len(self.means)
|
|
@@ -545,6 +551,7 @@ class OneHotEncoder(BaseTransformer):
|
|
|
545
551
|
_dummy_encoded = False
|
|
546
552
|
_indexer = None
|
|
547
553
|
_uniques = None
|
|
554
|
+
dtype = None
|
|
548
555
|
|
|
549
556
|
@staticmethod
|
|
550
557
|
def _prepare_data(data):
|
|
@@ -582,6 +589,7 @@ class OneHotEncoder(BaseTransformer):
|
|
|
582
589
|
data (pandas.Series or pandas.DataFrame):
|
|
583
590
|
Data to fit the transformer to.
|
|
584
591
|
"""
|
|
592
|
+
self.dtype = data.dtype
|
|
585
593
|
data = self._prepare_data(data)
|
|
586
594
|
|
|
587
595
|
null = pd.isna(data).to_numpy()
|
|
@@ -591,7 +599,7 @@ class OneHotEncoder(BaseTransformer):
|
|
|
591
599
|
self._indexer = list(range(self._num_dummies))
|
|
592
600
|
self.dummies = self._uniques.copy()
|
|
593
601
|
|
|
594
|
-
if not np.issubdtype(data.dtype, np.number):
|
|
602
|
+
if not np.issubdtype(data.dtype.type, np.number):
|
|
595
603
|
self._dummy_encoded = True
|
|
596
604
|
|
|
597
605
|
if self._dummy_na:
|
|
@@ -657,6 +665,7 @@ class OneHotEncoder(BaseTransformer):
|
|
|
657
665
|
Returns:
|
|
658
666
|
pandas.Series
|
|
659
667
|
"""
|
|
668
|
+
check_nan_in_transform(data, self.dtype)
|
|
660
669
|
if not isinstance(data, np.ndarray):
|
|
661
670
|
data = data.to_numpy()
|
|
662
671
|
|
|
@@ -664,8 +673,10 @@ class OneHotEncoder(BaseTransformer):
|
|
|
664
673
|
data = data.reshape(-1, 1)
|
|
665
674
|
|
|
666
675
|
indices = np.argmax(data, axis=1)
|
|
676
|
+
result = pd.Series(indices).map(self.dummies.__getitem__)
|
|
677
|
+
result = try_convert_to_dtype(result, self.dtype)
|
|
667
678
|
|
|
668
|
-
return
|
|
679
|
+
return result
|
|
669
680
|
|
|
670
681
|
|
|
671
682
|
class LabelEncoder(BaseTransformer):
|
|
@@ -801,13 +812,15 @@ class LabelEncoder(BaseTransformer):
|
|
|
801
812
|
Returns:
|
|
802
813
|
pandas.Series
|
|
803
814
|
"""
|
|
815
|
+
check_nan_in_transform(data, self.dtype)
|
|
804
816
|
if self.add_noise:
|
|
805
817
|
data = np.floor(data)
|
|
806
818
|
|
|
807
819
|
data = data.clip(min(self.values_to_categories), max(self.values_to_categories))
|
|
808
820
|
data = data.round().map(self.values_to_categories)
|
|
821
|
+
data = try_convert_to_dtype(data, self.dtype)
|
|
809
822
|
|
|
810
|
-
return data
|
|
823
|
+
return data
|
|
811
824
|
|
|
812
825
|
|
|
813
826
|
class OrderedLabelEncoder(LabelEncoder):
|
|
@@ -865,6 +878,7 @@ class OrderedLabelEncoder(LabelEncoder):
|
|
|
865
878
|
data (pandas.Series):
|
|
866
879
|
Data to fit the transformer to.
|
|
867
880
|
"""
|
|
881
|
+
self.dtype = data.dtype
|
|
868
882
|
data = data.fillna(np.nan)
|
|
869
883
|
missing = list(data[~data.isin(self.order)].unique())
|
|
870
884
|
if len(missing) > 0:
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Transformers for numerical data."""
|
|
2
2
|
import copy
|
|
3
|
-
import logging
|
|
4
|
-
import sys
|
|
5
3
|
import warnings
|
|
6
4
|
|
|
7
5
|
import numpy as np
|
|
@@ -11,11 +9,9 @@ from sklearn.mixture import BayesianGaussianMixture
|
|
|
11
9
|
|
|
12
10
|
from rdt.transformers.base import BaseTransformer
|
|
13
11
|
from rdt.transformers.null import NullTransformer
|
|
14
|
-
|
|
15
|
-
LOGGER = logging.getLogger(__name__)
|
|
12
|
+
from rdt.transformers.utils import learn_rounding_digits
|
|
16
13
|
|
|
17
14
|
EPSILON = np.finfo(np.float32).eps
|
|
18
|
-
MAX_DECIMALS = sys.float_info.dig - 1
|
|
19
15
|
INTEGER_BOUNDS = {
|
|
20
16
|
'Int8': (-2**7, 2**7 - 1),
|
|
21
17
|
'Int16': (-2**15, 2**15 - 1),
|
|
@@ -91,31 +87,6 @@ class FloatFormatter(BaseTransformer):
|
|
|
91
87
|
self.enforce_min_max_values = enforce_min_max_values
|
|
92
88
|
self.computer_representation = computer_representation
|
|
93
89
|
|
|
94
|
-
@staticmethod
|
|
95
|
-
def _learn_rounding_digits(data):
|
|
96
|
-
# check if data has any decimals
|
|
97
|
-
name = data.name
|
|
98
|
-
data = np.array(data)
|
|
99
|
-
roundable_data = data[~(np.isinf(data) | pd.isna(data))]
|
|
100
|
-
|
|
101
|
-
# Doesn't contain numbers
|
|
102
|
-
if len(roundable_data) == 0:
|
|
103
|
-
return None
|
|
104
|
-
|
|
105
|
-
# Doesn't contain decimal digits
|
|
106
|
-
if ((roundable_data % 1) == 0).all():
|
|
107
|
-
return 0
|
|
108
|
-
|
|
109
|
-
# Try to round to fewer digits
|
|
110
|
-
if (roundable_data == roundable_data.round(MAX_DECIMALS)).all():
|
|
111
|
-
for decimal in range(MAX_DECIMALS + 1):
|
|
112
|
-
if (roundable_data == roundable_data.round(decimal)).all():
|
|
113
|
-
return decimal
|
|
114
|
-
|
|
115
|
-
# Can't round, not equal after MAX_DECIMALS digits of precision
|
|
116
|
-
LOGGER.info("No rounding scheme detected for column '%s'. Data will not be rounded.", name)
|
|
117
|
-
return None
|
|
118
|
-
|
|
119
90
|
def _raise_out_of_bounds_error(self, value, name, bound_type, min_bound, max_bound):
|
|
120
91
|
raise ValueError(
|
|
121
92
|
f"The {bound_type} value in column '{name}' is {value}."
|
|
@@ -158,7 +129,7 @@ class FloatFormatter(BaseTransformer):
|
|
|
158
129
|
self._max_value = data.max()
|
|
159
130
|
|
|
160
131
|
if self.learn_rounding_scheme:
|
|
161
|
-
self._rounding_digits =
|
|
132
|
+
self._rounding_digits = learn_rounding_digits(data)
|
|
162
133
|
|
|
163
134
|
self.null_transformer = NullTransformer(
|
|
164
135
|
self.missing_value_replacement,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""Transformers for text data."""
|
|
2
|
+
import logging
|
|
2
3
|
import warnings
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
5
6
|
import pandas as pd
|
|
6
7
|
|
|
7
|
-
from rdt.errors import TransformerProcessingError
|
|
8
8
|
from rdt.transformers.base import BaseTransformer
|
|
9
9
|
from rdt.transformers.utils import strings_from_regex
|
|
10
10
|
|
|
11
|
+
LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
class IDGenerator(BaseTransformer):
|
|
13
15
|
"""Generate an ID column.
|
|
@@ -139,6 +141,37 @@ class RegexGenerator(BaseTransformer):
|
|
|
139
141
|
"""Drop the input column by returning ``None``."""
|
|
140
142
|
return None
|
|
141
143
|
|
|
144
|
+
def _warn_not_enough_unique_values(self, sample_size):
|
|
145
|
+
"""Warn the user that the regex cannot generate enough unique values.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
sample_size (int):
|
|
149
|
+
Number of samples to be generated.
|
|
150
|
+
"""
|
|
151
|
+
warned = False
|
|
152
|
+
if sample_size > self.generator_size:
|
|
153
|
+
if self.enforce_uniqueness:
|
|
154
|
+
warnings.warn(
|
|
155
|
+
f"The regex for '{self.get_input_column()}' can only generate "
|
|
156
|
+
f'{self.generator_size} unique values. Additional values may not exactly '
|
|
157
|
+
'follow the provided regex.'
|
|
158
|
+
)
|
|
159
|
+
warned = True
|
|
160
|
+
else:
|
|
161
|
+
LOGGER.info(
|
|
162
|
+
"The data has %s rows but the regex for '%s' can only create %s unique values."
|
|
163
|
+
" Some values in '%s' may be repeated.",
|
|
164
|
+
sample_size, self.get_input_column(), self.generator_size,
|
|
165
|
+
self.get_input_column()
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
remaining = self.generator_size - self.generated
|
|
169
|
+
if sample_size > remaining and self.enforce_uniqueness and not warned:
|
|
170
|
+
warnings.warn(
|
|
171
|
+
f'The regex generator is not able to generate {sample_size} new unique '
|
|
172
|
+
f'values (only {max(remaining, 0)} unique values left).'
|
|
173
|
+
)
|
|
174
|
+
|
|
142
175
|
def _reverse_transform(self, data):
|
|
143
176
|
"""Generate new data using the provided ``regex_format``.
|
|
144
177
|
|
|
@@ -154,47 +187,39 @@ class RegexGenerator(BaseTransformer):
|
|
|
154
187
|
else:
|
|
155
188
|
sample_size = self.data_length
|
|
156
189
|
|
|
157
|
-
|
|
158
|
-
if self.enforce_uniqueness:
|
|
159
|
-
raise TransformerProcessingError(
|
|
160
|
-
f'The regex is not able to generate {sample_size} unique values. '
|
|
161
|
-
f"Please use a different regex for column ('{self.get_input_column()}')."
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
warnings.warn(
|
|
165
|
-
f"The data has {sample_size} rows but the regex for '{self.get_input_column()}' "
|
|
166
|
-
f'can only create {self.generator_size} unique values. Some values in '
|
|
167
|
-
f"'{self.get_input_column()}' may be repeated."
|
|
168
|
-
)
|
|
190
|
+
self._warn_not_enough_unique_values(sample_size)
|
|
169
191
|
|
|
170
192
|
remaining = self.generator_size - self.generated
|
|
171
|
-
if sample_size >
|
|
172
|
-
if self.enforce_uniqueness:
|
|
173
|
-
raise TransformerProcessingError(
|
|
174
|
-
f'The regex generator is not able to generate {sample_size} new unique '
|
|
175
|
-
f'values (only {remaining} unique value left). Please use '
|
|
176
|
-
"'reset_randomization' in order to restart the generator."
|
|
177
|
-
)
|
|
178
|
-
|
|
193
|
+
if sample_size > remaining:
|
|
179
194
|
self.reset_randomization()
|
|
180
195
|
remaining = self.generator_size
|
|
181
196
|
|
|
182
197
|
if remaining >= sample_size:
|
|
183
|
-
reverse_transformed =
|
|
184
|
-
next(self.generator)
|
|
185
|
-
for _ in range(sample_size)
|
|
186
|
-
], dtype=object)
|
|
187
|
-
|
|
198
|
+
reverse_transformed = [next(self.generator) for _ in range(sample_size)]
|
|
188
199
|
self.generated += sample_size
|
|
189
200
|
|
|
190
201
|
else:
|
|
191
|
-
self.generated = self.generator_size
|
|
192
202
|
generated_values = list(self.generator)
|
|
193
|
-
reverse_transformed = []
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
reverse_transformed = generated_values[:]
|
|
204
|
+
self.generated = self.generator_size
|
|
205
|
+
if self.enforce_uniqueness:
|
|
206
|
+
try:
|
|
207
|
+
remaining_samples = sample_size - len(reverse_transformed)
|
|
208
|
+
start = int(generated_values[-1]) + 1
|
|
209
|
+
reverse_transformed.extend(
|
|
210
|
+
[str(i) for i in range(start, start + remaining_samples)])
|
|
211
|
+
|
|
212
|
+
except ValueError:
|
|
213
|
+
counter = 0
|
|
214
|
+
while len(reverse_transformed) < sample_size:
|
|
215
|
+
remaining_samples = sample_size - len(reverse_transformed)
|
|
216
|
+
reverse_transformed.extend(
|
|
217
|
+
[f'{i}({counter})' for i in generated_values[:remaining_samples]])
|
|
218
|
+
counter += 1
|
|
219
|
+
|
|
220
|
+
else:
|
|
221
|
+
while len(reverse_transformed) < sample_size:
|
|
222
|
+
remaining_samples = sample_size - len(reverse_transformed)
|
|
223
|
+
reverse_transformed.extend(generated_values[:remaining_samples])
|
|
224
|
+
|
|
225
|
+
return np.array(reverse_transformed, dtype=object)
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
"""Tools to generate strings from regular expressions."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import re
|
|
4
5
|
import string
|
|
6
|
+
import sys
|
|
7
|
+
import warnings
|
|
5
8
|
|
|
6
9
|
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
7
11
|
|
|
8
12
|
import sre_parse # isort:skip
|
|
9
13
|
|
|
14
|
+
LOGGER = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
MAX_DECIMALS = sys.float_info.dig - 1
|
|
17
|
+
|
|
10
18
|
|
|
11
19
|
def _literal(character, max_repeat):
|
|
12
20
|
del max_repeat
|
|
@@ -149,7 +157,7 @@ def strings_from_regex(regex, max_repeat=16):
|
|
|
149
157
|
generators.append((generator, option, args))
|
|
150
158
|
sizes.append(size)
|
|
151
159
|
|
|
152
|
-
return _from_generators(generators, max_repeat), np.prod(sizes, dtype=np.complex128)
|
|
160
|
+
return _from_generators(generators, max_repeat), np.prod(sizes, dtype=np.complex128).real
|
|
153
161
|
|
|
154
162
|
|
|
155
163
|
def fill_nan_with_none(data):
|
|
@@ -184,3 +192,86 @@ def flatten_column_list(column_list):
|
|
|
184
192
|
flattened.append(column)
|
|
185
193
|
|
|
186
194
|
return flattened
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def check_nan_in_transform(data, dtype):
|
|
198
|
+
"""Check if there are null values in the transformed data.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
data (pd.Series or numpy.ndarray):
|
|
202
|
+
Data that has been transformed.
|
|
203
|
+
dtype (str):
|
|
204
|
+
Data type of the transformed data.
|
|
205
|
+
"""
|
|
206
|
+
if pd.isna(data).any().any():
|
|
207
|
+
message = (
|
|
208
|
+
'There are null values in the transformed data. The reversed '
|
|
209
|
+
'transformed data will contain null values'
|
|
210
|
+
)
|
|
211
|
+
is_integer = pd.api.types.is_integer_dtype(dtype)
|
|
212
|
+
if is_integer:
|
|
213
|
+
message += " of type 'float'."
|
|
214
|
+
else:
|
|
215
|
+
message += '.'
|
|
216
|
+
|
|
217
|
+
warnings.warn(message)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def try_convert_to_dtype(data, dtype):
|
|
221
|
+
"""Try to convert data to a given dtype.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
data (pd.Series or numpy.ndarray):
|
|
225
|
+
Data to convert.
|
|
226
|
+
dtype (str):
|
|
227
|
+
Data type to convert to.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
data:
|
|
231
|
+
Data converted to the given dtype.
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
data = data.astype(dtype)
|
|
235
|
+
except ValueError as error:
|
|
236
|
+
is_integer = pd.api.types.is_integer_dtype(dtype)
|
|
237
|
+
if is_integer:
|
|
238
|
+
data = data.astype(float)
|
|
239
|
+
else:
|
|
240
|
+
raise error
|
|
241
|
+
|
|
242
|
+
return data
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def learn_rounding_digits(data):
|
|
246
|
+
"""Learn the number of digits to round data to.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
data (pd.Series):
|
|
250
|
+
Data to learn the number of digits to round to.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
int or None:
|
|
254
|
+
Number of digits to round to.
|
|
255
|
+
"""
|
|
256
|
+
# check if data has any decimals
|
|
257
|
+
name = data.name
|
|
258
|
+
data = np.array(data)
|
|
259
|
+
roundable_data = data[~(np.isinf(data) | pd.isna(data))]
|
|
260
|
+
|
|
261
|
+
# Doesn't contain numbers
|
|
262
|
+
if len(roundable_data) == 0:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
# Doesn't contain decimal digits
|
|
266
|
+
if ((roundable_data % 1) == 0).all():
|
|
267
|
+
return 0
|
|
268
|
+
|
|
269
|
+
# Try to round to fewer digits
|
|
270
|
+
if (roundable_data == roundable_data.round(MAX_DECIMALS)).all():
|
|
271
|
+
for decimal in range(MAX_DECIMALS + 1):
|
|
272
|
+
if (roundable_data == roundable_data.round(decimal)).all():
|
|
273
|
+
return decimal
|
|
274
|
+
|
|
275
|
+
# Can't round, not equal after MAX_DECIMALS digits of precision
|
|
276
|
+
LOGGER.info("No rounding scheme detected for column '%s'. Data will not be rounded.", name)
|
|
277
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rdt
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.2.dev0
|
|
4
4
|
Summary: Reversible Data Transforms
|
|
5
5
|
Home-page: https://github.com/sdv-dev/RDT
|
|
6
6
|
Author: DataCebo, Inc.
|
|
@@ -225,6 +225,14 @@ for specific needs.
|
|
|
225
225
|
|
|
226
226
|
# History
|
|
227
227
|
|
|
228
|
+
## 1.9.1 - 2024-01-10
|
|
229
|
+
|
|
230
|
+
This release fixes a bug that caused the `AnonymizedFaker` to crash with provider/function combinations that return tuples.
|
|
231
|
+
|
|
232
|
+
### Bugs Fixed
|
|
233
|
+
|
|
234
|
+
* AnonymizedFaker crashes with ValueError for specific provider/function pairs (eg. currency) - Issue [#743](https://github.com/sdv-dev/RDT/issues/743) by @ R-Palazzo
|
|
235
|
+
|
|
228
236
|
## 1.9.0 - 2023-11-14
|
|
229
237
|
|
|
230
238
|
This release adds a parameter to the `UnixTimestampEncoder` and `OptimizedTimestampEncoder`, called `enforce_min_max_values`. When this is set to True, it clips all values in the reverse transformed data to the min and max datetimes seen in the fitted data.
|