Skip to content

Commit 74ac350

Browse files
committed
make release-tag: Merge branch 'main' into stable
2 parents de68529 + 75705c5 commit 74ac350

29 files changed

+926
-756
lines changed

.github/workflows/dependency_checker.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
run: |
1717
python -m pip install .[dev]
1818
make check-deps OUTPUT_FILEPATH=latest_requirements.txt
19+
make fix-lint
1920
- name: Create pull request
2021
id: cpr
2122
uses: peter-evans/create-pull-request@v4

HISTORY.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# History
22

3+
## v0.12.0 - 2024-11-12
4+
5+
### Maintenance
6+
7+
* Modularize `fit` Method of GaussianMultivariate to Improve Exception Handling - Issue [#431](https://github.com/sdv-dev/Copulas/issues/431) by @pvk-developer
8+
* Move `__init__.py` utility functions to a `utils.py` module. - Issue [#428](https://github.com/sdv-dev/Copulas/issues/428) by @pvk-developer
9+
* AttributeError raised when an entry point fails to load - Issue [#427](https://github.com/sdv-dev/Copulas/issues/427) by @pvk-developer
10+
311
## v0.11.1 - 2024-08-21
412

513
### Maintenance

copulas/__init__.py

Lines changed: 9 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -1,266 +1,15 @@
1-
# -*- coding: utf-8 -*-
2-
31
"""Top-level package for Copulas."""
42

53
__author__ = 'DataCebo, Inc.'
64
__email__ = '[email protected]'
7-
__version__ = '0.11.1'
5+
__version__ = '0.12.0.dev1'
86

9-
import contextlib
10-
import importlib
117
import sys
128
import warnings
139
from copy import deepcopy
1410
from importlib.metadata import entry_points
1511
from operator import attrgetter
16-
17-
import numpy as np
18-
import pandas as pd
19-
20-
EPSILON = np.finfo(np.float32).eps
21-
22-
23-
class NotFittedError(Exception):
24-
"""NotFittedError class."""
25-
26-
27-
@contextlib.contextmanager
28-
def set_random_state(random_state, set_model_random_state):
29-
"""Context manager for managing the random state.
30-
31-
Args:
32-
random_state (int or np.random.RandomState):
33-
The random seed or RandomState.
34-
set_model_random_state (function):
35-
Function to set the random state on the model.
36-
"""
37-
original_state = np.random.get_state()
38-
39-
np.random.set_state(random_state.get_state())
40-
41-
try:
42-
yield
43-
finally:
44-
current_random_state = np.random.RandomState()
45-
current_random_state.set_state(np.random.get_state())
46-
set_model_random_state(current_random_state)
47-
np.random.set_state(original_state)
48-
49-
50-
def random_state(function):
51-
"""Set the random state before calling the function.
52-
53-
Args:
54-
function (Callable):
55-
The function to wrap around.
56-
"""
57-
58-
def wrapper(self, *args, **kwargs):
59-
if self.random_state is None:
60-
return function(self, *args, **kwargs)
61-
62-
else:
63-
with set_random_state(self.random_state, self.set_random_state):
64-
return function(self, *args, **kwargs)
65-
66-
return wrapper
67-
68-
69-
def validate_random_state(random_state):
70-
"""Validate random state argument.
71-
72-
Args:
73-
random_state (int, numpy.random.RandomState, tuple, or None):
74-
Seed or RandomState for the random generator.
75-
76-
Output:
77-
numpy.random.RandomState
78-
"""
79-
if random_state is None:
80-
return None
81-
82-
if isinstance(random_state, int):
83-
return np.random.RandomState(seed=random_state)
84-
elif isinstance(random_state, np.random.RandomState):
85-
return random_state
86-
else:
87-
raise TypeError(
88-
f'`random_state` {random_state} expected to be an int '
89-
'or `np.random.RandomState` object.'
90-
)
91-
92-
93-
def get_instance(obj, **kwargs):
94-
"""Create new instance of the ``obj`` argument.
95-
96-
Args:
97-
obj (str, type, instance):
98-
"""
99-
instance = None
100-
if isinstance(obj, str):
101-
package, name = obj.rsplit('.', 1)
102-
instance = getattr(importlib.import_module(package), name)(**kwargs)
103-
elif isinstance(obj, type):
104-
instance = obj(**kwargs)
105-
else:
106-
if kwargs:
107-
instance = obj.__class__(**kwargs)
108-
else:
109-
args = getattr(obj, '__args__', ())
110-
kwargs = getattr(obj, '__kwargs__', {})
111-
instance = obj.__class__(*args, **kwargs)
112-
113-
return instance
114-
115-
116-
def store_args(__init__):
117-
"""Save ``*args`` and ``**kwargs`` used in the ``__init__`` of a copula.
118-
119-
Args:
120-
__init__(callable): ``__init__`` function to store their arguments.
121-
122-
Returns:
123-
callable: Decorated ``__init__`` function.
124-
"""
125-
126-
def new__init__(self, *args, **kwargs):
127-
args_copy = deepcopy(args)
128-
kwargs_copy = deepcopy(kwargs)
129-
__init__(self, *args, **kwargs)
130-
self.__args__ = args_copy
131-
self.__kwargs__ = kwargs_copy
132-
133-
return new__init__
134-
135-
136-
def get_qualified_name(_object):
137-
"""Return the Fully Qualified Name from an instance or class."""
138-
module = _object.__module__
139-
if hasattr(_object, '__name__'):
140-
_class = _object.__name__
141-
142-
else:
143-
_class = _object.__class__.__name__
144-
145-
return module + '.' + _class
146-
147-
148-
def vectorize(function):
149-
"""Allow a method that only accepts scalars to accept vectors too.
150-
151-
This decorator has two different behaviors depending on the dimensionality of the
152-
array passed as an argument:
153-
154-
**1-d array**
155-
156-
It will work under the assumption that the `function` argument is a callable
157-
with signature::
158-
159-
function(self, X, *args, **kwargs)
160-
161-
where X is an scalar magnitude.
162-
163-
In this case the arguments of the input array will be given one at a time, and
164-
both the input and output of the decorated function will have shape (n,).
165-
166-
**2-d array**
167-
168-
It will work under the assumption that the `function` argument is a callable with signature::
169-
170-
function(self, X0, ..., Xj, *args, **kwargs)
171-
172-
where `Xi` are scalar magnitudes.
173-
174-
It will pass the contents of each row unpacked on each call. The input is espected to have
175-
shape (n, j), the output a shape of (n,)
176-
177-
It will return a function that is guaranteed to return a `numpy.array`.
178-
179-
Args:
180-
function(callable): Function that only accept and return scalars.
181-
182-
Returns:
183-
callable: Decorated function that can accept and return :attr:`numpy.array`.
184-
185-
"""
186-
187-
def decorated(self, X, *args, **kwargs):
188-
if not isinstance(X, np.ndarray):
189-
return function(self, X, *args, **kwargs)
190-
191-
if len(X.shape) == 1:
192-
X = X.reshape([-1, 1])
193-
194-
if len(X.shape) == 2:
195-
return np.fromiter(
196-
(function(self, *x, *args, **kwargs) for x in X), np.dtype('float64')
197-
)
198-
199-
else:
200-
raise ValueError('Arrays of dimensionality higher than 2 are not supported.')
201-
202-
decorated.__doc__ = function.__doc__
203-
return decorated
204-
205-
206-
def scalarize(function):
207-
"""Allow methods that only accepts 1-d vectors to work with scalars.
208-
209-
Args:
210-
function(callable): Function that accepts and returns vectors.
211-
212-
Returns:
213-
callable: Decorated function that accepts and returns scalars.
214-
"""
215-
216-
def decorated(self, X, *args, **kwargs):
217-
scalar = not isinstance(X, np.ndarray)
218-
219-
if scalar:
220-
X = np.array([X])
221-
222-
result = function(self, X, *args, **kwargs)
223-
if scalar:
224-
result = result[0]
225-
226-
return result
227-
228-
decorated.__doc__ = function.__doc__
229-
return decorated
230-
231-
232-
def check_valid_values(function):
233-
"""Raise an exception if the given values are not supported.
234-
235-
Args:
236-
function(callable): Method whose unique argument is a numpy.array-like object.
237-
238-
Returns:
239-
callable: Decorated function
240-
241-
Raises:
242-
ValueError: If there are missing or invalid values or if the dataset is empty.
243-
"""
244-
245-
def decorated(self, X, *args, **kwargs):
246-
if isinstance(X, pd.DataFrame):
247-
W = X.to_numpy()
248-
249-
else:
250-
W = X
251-
252-
if not len(W):
253-
raise ValueError('Your dataset is empty.')
254-
255-
if not (np.issubdtype(W.dtype, np.floating) or np.issubdtype(W.dtype, np.integer)):
256-
raise ValueError('There are non-numerical values in your data.')
257-
258-
if np.isnan(W).any().any():
259-
raise ValueError('There are nan values in your data.')
260-
261-
return function(self, X, *args, **kwargs)
262-
263-
return decorated
12+
from types import ModuleType
26413

26514

26615
def _get_addon_target(addon_path_name):
@@ -319,8 +68,8 @@ def _find_addons():
31968
for entry_point in eps:
32069
try:
32170
addon = entry_point.load()
322-
except Exception: # pylint: disable=broad-exception-caught
323-
msg = f'Failed to load "{entry_point.name}" from "{entry_point.value}".'
71+
except Exception as e: # pylint: disable=broad-exception-caught
72+
msg = f'Failed to load "{entry_point.name}" from "{entry_point.value}" with error:\n{e}'
32473
warnings.warn(msg)
32574
continue
32675

@@ -331,6 +80,11 @@ def _find_addons():
33180
warnings.warn(msg)
33281
continue
33382

83+
if isinstance(addon, ModuleType):
84+
addon_module_name = f'{addon_target.__name__}.{addon_name}'
85+
if addon_module_name not in sys.modules:
86+
sys.modules[addon_module_name] = addon
87+
33488
setattr(addon_target, addon_name, addon)
33589

33690

copulas/bivariate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44
import pandas as pd
55

6-
from copulas import EPSILON
6+
from copulas.utils import EPSILON
77
from copulas.bivariate.base import Bivariate, CopulaTypes
88
from copulas.bivariate.clayton import Clayton
99
from copulas.bivariate.frank import Frank

copulas/bivariate/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
from scipy import stats
99
from scipy.optimize import brentq
1010

11-
from copulas import EPSILON, NotFittedError, random_state, validate_random_state
1211
from copulas.bivariate.utils import split_matrix
12+
from copulas.errors import NotFittedError
13+
from copulas.utils import EPSILON, random_state, validate_random_state
1314

1415

1516
class CopulaTypes(Enum):

copulas/bivariate/frank.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import scipy.integrate as integrate
77
from scipy.optimize import least_squares
88

9-
from copulas import EPSILON
109
from copulas.bivariate.base import Bivariate, CopulaTypes
1110
from copulas.bivariate.utils import split_matrix
11+
from copulas.utils import EPSILON
1212

1313
MIN_FLOAT_LOG = np.log(sys.float_info.min)
1414
MAX_FLOAT_LOG = np.log(sys.float_info.max)

copulas/datasets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pandas as pd
55
from scipy import stats
66

7-
from copulas import set_random_state, validate_random_state
7+
from copulas.utils import set_random_state, validate_random_state
88

99

1010
def _dummy_fn(state):

copulas/errors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Copulas Exceptions."""
2+
3+
4+
class NotFittedError(Exception):
5+
"""NotFittedError class."""

copulas/multivariate/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
import numpy as np
66

7-
from copulas import NotFittedError, get_instance, validate_random_state
7+
from copulas.errors import NotFittedError
8+
from copulas.utils import get_instance, validate_random_state
89

910

1011
class Multivariate(object):

0 commit comments

Comments
 (0)