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.
Files changed (92) hide show
  1. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/HISTORY.md +8 -0
  2. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/PKG-INFO +9 -1
  3. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/__init__.py +1 -1
  4. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/hyper_transformer.py +31 -0
  5. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/base.py +4 -0
  6. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/categorical.py +19 -5
  7. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/numerical.py +2 -31
  8. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/text.py +60 -35
  9. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/utils.py +92 -1
  10. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/PKG-INFO +9 -1
  11. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/setup.cfg +1 -1
  12. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/setup.py +1 -1
  13. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/test_hyper_transformer.py +103 -1
  14. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_base.py +2 -0
  15. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_categorical.py +30 -0
  16. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_text.py +34 -0
  17. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/test_hyper_transformer.py +121 -10
  18. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_base.py +6 -0
  19. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_categorical.py +78 -5
  20. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_numerical.py +0 -80
  21. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_text.py +87 -33
  22. rdt-1.9.2.dev0/tests/unit/transformers/test_utils.py +215 -0
  23. rdt-1.9.1.dev0/tests/unit/transformers/test_utils.py +0 -68
  24. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/AUTHORS.rst +0 -0
  25. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/CONTRIBUTING.rst +0 -0
  26. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/LICENSE +0 -0
  27. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/MANIFEST.in +0 -0
  28. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/README.md +0 -0
  29. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/RELEASE.md +0 -0
  30. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/errors.py +0 -0
  31. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/__init__.py +0 -0
  32. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/__init__.py +0 -0
  33. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/base.py +0 -0
  34. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/boolean.py +0 -0
  35. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/categorical.py +0 -0
  36. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/datetime.py +0 -0
  37. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/numerical.py +0 -0
  38. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/pii.py +0 -0
  39. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/text.py +0 -0
  40. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/datasets/utils.py +0 -0
  41. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/performance.py +0 -0
  42. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/performance/profiling.py +0 -0
  43. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/__init__.py +0 -0
  44. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/addons/addons_setup.py +0 -0
  45. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/boolean.py +0 -0
  46. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/datetime.py +0 -0
  47. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/null.py +0 -0
  48. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/pii/__init__.py +0 -0
  49. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/pii/anonymization.py +0 -0
  50. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/pii/anonymizer.py +0 -0
  51. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt/transformers/pii/utils.py +0 -0
  52. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/SOURCES.txt +0 -0
  53. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/dependency_links.txt +0 -0
  54. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/not-zip-safe +0 -0
  55. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/requires.txt +0 -0
  56. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/rdt.egg-info/top_level.txt +0 -0
  57. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/__init__.py +0 -0
  58. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/code_style.py +0 -0
  59. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/contributing.py +0 -0
  60. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_boolean.py +0 -0
  61. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_categorical.py +0 -0
  62. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_datetime.py +0 -0
  63. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_numerical.py +0 -0
  64. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/datasets/tests/test_utils.py +0 -0
  65. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/__init__.py +0 -0
  66. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/test_transformers.py +0 -0
  67. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/__init__.py +0 -0
  68. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/pii/__init__.py +0 -0
  69. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/pii/test_anonymization.py +0 -0
  70. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/pii/test_anonymizer.py +0 -0
  71. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_boolean.py +0 -0
  72. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_datetime.py +0 -0
  73. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/integration/transformers/test_numerical.py +0 -0
  74. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/README.md +0 -0
  75. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/__init__.py +0 -0
  76. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/test_performance.py +0 -0
  77. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/tests/__init__.py +0 -0
  78. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/performance/tests/test_profiling.py +0 -0
  79. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/__init__.py +0 -0
  80. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/test___init__.py +0 -0
  81. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/__init__.py +0 -0
  82. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/addons/__init__.py +0 -0
  83. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/addons/identity/__init__.py +0 -0
  84. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/addons/identity/test_identity.py +0 -0
  85. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/pii/__init__.py +0 -0
  86. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/pii/test_anonymization.py +0 -0
  87. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/pii/test_anonymizer.py +0 -0
  88. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/pii/test_utils.py +0 -0
  89. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test___init__.py +0 -0
  90. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_boolean.py +0 -0
  91. {rdt-1.9.1.dev0 → rdt-1.9.2.dev0}/tests/unit/transformers/test_datetime.py +0 -0
  92. {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.1.dev0
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.
@@ -5,7 +5,7 @@
5
5
 
6
6
  __author__ = 'DataCebo, Inc.'
7
7
  __email__ = 'info@sdv.dev'
8
- __version__ = '1.9.1.dev0'
8
+ __version__ = '1.9.2.dev0'
9
9
 
10
10
 
11
11
  import sys
@@ -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
- return result.replace(nan_name, np.nan).astype(self.dtype)
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 pd.Series(indices).map(self.dummies.__getitem__)
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.astype(self.dtype)
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 = self._learn_rounding_digits(data)
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
- if sample_size > self.generator_size:
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 > self.generator_size - self.generated:
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 = np.array([
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
- while len(reverse_transformed) < sample_size:
195
- remaining_samples = sample_size - len(reverse_transformed)
196
- reverse_transformed.extend(generated_values[:remaining_samples])
197
-
198
- reverse_transformed = np.array(reverse_transformed, dtype=object)
199
-
200
- return reverse_transformed
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.1.dev0
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.
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 1.9.1.dev0
2
+ current_version = 1.9.2.dev0
3
3
  commit = True
4
4
  tag = True
5
5
  parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<candidate>\d+))?
@@ -138,6 +138,6 @@ setup(
138
138
  test_suite='tests',
139
139
  tests_require=tests_require,
140
140
  url='https://github.com/sdv-dev/RDT',
141
- version='1.9.1.dev0',
141
+ version='1.9.2.dev0',
142
142
  zip_safe=False,
143
143
  )