Skip to content

Commit

Permalink
make release-tag: Merge branch 'main' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
amontanez24 committed Nov 13, 2024
2 parents de68529 + 75705c5 commit 74ac350
Show file tree
Hide file tree
Showing 29 changed files with 926 additions and 756 deletions.
1 change: 1 addition & 0 deletions .github/workflows/dependency_checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
run: |
python -m pip install .[dev]
make check-deps OUTPUT_FILEPATH=latest_requirements.txt
make fix-lint
- name: Create pull request
id: cpr
uses: peter-evans/create-pull-request@v4
Expand Down
8 changes: 8 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# History

## v0.12.0 - 2024-11-12

### Maintenance

* Modularize `fit` Method of GaussianMultivariate to Improve Exception Handling - Issue [#431](https://github.com/sdv-dev/Copulas/issues/431) by @pvk-developer
* Move `__init__.py` utility functions to a `utils.py` module. - Issue [#428](https://github.com/sdv-dev/Copulas/issues/428) by @pvk-developer
* AttributeError raised when an entry point fails to load - Issue [#427](https://github.com/sdv-dev/Copulas/issues/427) by @pvk-developer

## v0.11.1 - 2024-08-21

### Maintenance
Expand Down
264 changes: 9 additions & 255 deletions copulas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,266 +1,15 @@
# -*- coding: utf-8 -*-

"""Top-level package for Copulas."""

__author__ = 'DataCebo, Inc.'
__email__ = '[email protected]'
__version__ = '0.11.1'
__version__ = '0.12.0.dev1'

import contextlib
import importlib
import sys
import warnings
from copy import deepcopy
from importlib.metadata import entry_points
from operator import attrgetter

import numpy as np
import pandas as pd

EPSILON = np.finfo(np.float32).eps


class NotFittedError(Exception):
"""NotFittedError class."""


@contextlib.contextmanager
def set_random_state(random_state, set_model_random_state):
"""Context manager for managing the random state.
Args:
random_state (int or np.random.RandomState):
The random seed or RandomState.
set_model_random_state (function):
Function to set the random state on the model.
"""
original_state = np.random.get_state()

np.random.set_state(random_state.get_state())

try:
yield
finally:
current_random_state = np.random.RandomState()
current_random_state.set_state(np.random.get_state())
set_model_random_state(current_random_state)
np.random.set_state(original_state)


def random_state(function):
"""Set the random state before calling the function.
Args:
function (Callable):
The function to wrap around.
"""

def wrapper(self, *args, **kwargs):
if self.random_state is None:
return function(self, *args, **kwargs)

else:
with set_random_state(self.random_state, self.set_random_state):
return function(self, *args, **kwargs)

return wrapper


def validate_random_state(random_state):
"""Validate random state argument.
Args:
random_state (int, numpy.random.RandomState, tuple, or None):
Seed or RandomState for the random generator.
Output:
numpy.random.RandomState
"""
if random_state is None:
return None

if isinstance(random_state, int):
return np.random.RandomState(seed=random_state)
elif isinstance(random_state, np.random.RandomState):
return random_state
else:
raise TypeError(
f'`random_state` {random_state} expected to be an int '
'or `np.random.RandomState` object.'
)


def get_instance(obj, **kwargs):
"""Create new instance of the ``obj`` argument.
Args:
obj (str, type, instance):
"""
instance = None
if isinstance(obj, str):
package, name = obj.rsplit('.', 1)
instance = getattr(importlib.import_module(package), name)(**kwargs)
elif isinstance(obj, type):
instance = obj(**kwargs)
else:
if kwargs:
instance = obj.__class__(**kwargs)
else:
args = getattr(obj, '__args__', ())
kwargs = getattr(obj, '__kwargs__', {})
instance = obj.__class__(*args, **kwargs)

return instance


def store_args(__init__):
"""Save ``*args`` and ``**kwargs`` used in the ``__init__`` of a copula.
Args:
__init__(callable): ``__init__`` function to store their arguments.
Returns:
callable: Decorated ``__init__`` function.
"""

def new__init__(self, *args, **kwargs):
args_copy = deepcopy(args)
kwargs_copy = deepcopy(kwargs)
__init__(self, *args, **kwargs)
self.__args__ = args_copy
self.__kwargs__ = kwargs_copy

return new__init__


def get_qualified_name(_object):
"""Return the Fully Qualified Name from an instance or class."""
module = _object.__module__
if hasattr(_object, '__name__'):
_class = _object.__name__

else:
_class = _object.__class__.__name__

return module + '.' + _class


def vectorize(function):
"""Allow a method that only accepts scalars to accept vectors too.
This decorator has two different behaviors depending on the dimensionality of the
array passed as an argument:
**1-d array**
It will work under the assumption that the `function` argument is a callable
with signature::
function(self, X, *args, **kwargs)
where X is an scalar magnitude.
In this case the arguments of the input array will be given one at a time, and
both the input and output of the decorated function will have shape (n,).
**2-d array**
It will work under the assumption that the `function` argument is a callable with signature::
function(self, X0, ..., Xj, *args, **kwargs)
where `Xi` are scalar magnitudes.
It will pass the contents of each row unpacked on each call. The input is espected to have
shape (n, j), the output a shape of (n,)
It will return a function that is guaranteed to return a `numpy.array`.
Args:
function(callable): Function that only accept and return scalars.
Returns:
callable: Decorated function that can accept and return :attr:`numpy.array`.
"""

def decorated(self, X, *args, **kwargs):
if not isinstance(X, np.ndarray):
return function(self, X, *args, **kwargs)

if len(X.shape) == 1:
X = X.reshape([-1, 1])

if len(X.shape) == 2:
return np.fromiter(
(function(self, *x, *args, **kwargs) for x in X), np.dtype('float64')
)

else:
raise ValueError('Arrays of dimensionality higher than 2 are not supported.')

decorated.__doc__ = function.__doc__
return decorated


def scalarize(function):
"""Allow methods that only accepts 1-d vectors to work with scalars.
Args:
function(callable): Function that accepts and returns vectors.
Returns:
callable: Decorated function that accepts and returns scalars.
"""

def decorated(self, X, *args, **kwargs):
scalar = not isinstance(X, np.ndarray)

if scalar:
X = np.array([X])

result = function(self, X, *args, **kwargs)
if scalar:
result = result[0]

return result

decorated.__doc__ = function.__doc__
return decorated


def check_valid_values(function):
"""Raise an exception if the given values are not supported.
Args:
function(callable): Method whose unique argument is a numpy.array-like object.
Returns:
callable: Decorated function
Raises:
ValueError: If there are missing or invalid values or if the dataset is empty.
"""

def decorated(self, X, *args, **kwargs):
if isinstance(X, pd.DataFrame):
W = X.to_numpy()

else:
W = X

if not len(W):
raise ValueError('Your dataset is empty.')

if not (np.issubdtype(W.dtype, np.floating) or np.issubdtype(W.dtype, np.integer)):
raise ValueError('There are non-numerical values in your data.')

if np.isnan(W).any().any():
raise ValueError('There are nan values in your data.')

return function(self, X, *args, **kwargs)

return decorated
from types import ModuleType


def _get_addon_target(addon_path_name):
Expand Down Expand Up @@ -319,8 +68,8 @@ def _find_addons():
for entry_point in eps:
try:
addon = entry_point.load()
except Exception: # pylint: disable=broad-exception-caught
msg = f'Failed to load "{entry_point.name}" from "{entry_point.value}".'
except Exception as e: # pylint: disable=broad-exception-caught
msg = f'Failed to load "{entry_point.name}" from "{entry_point.value}" with error:\n{e}'
warnings.warn(msg)
continue

Expand All @@ -331,6 +80,11 @@ def _find_addons():
warnings.warn(msg)
continue

if isinstance(addon, ModuleType):
addon_module_name = f'{addon_target.__name__}.{addon_name}'
if addon_module_name not in sys.modules:
sys.modules[addon_module_name] = addon

setattr(addon_target, addon_name, addon)


Expand Down
2 changes: 1 addition & 1 deletion copulas/bivariate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np
import pandas as pd

from copulas import EPSILON
from copulas.utils import EPSILON
from copulas.bivariate.base import Bivariate, CopulaTypes
from copulas.bivariate.clayton import Clayton
from copulas.bivariate.frank import Frank
Expand Down
3 changes: 2 additions & 1 deletion copulas/bivariate/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
from scipy import stats
from scipy.optimize import brentq

from copulas import EPSILON, NotFittedError, random_state, validate_random_state
from copulas.bivariate.utils import split_matrix
from copulas.errors import NotFittedError
from copulas.utils import EPSILON, random_state, validate_random_state


class CopulaTypes(Enum):
Expand Down
2 changes: 1 addition & 1 deletion copulas/bivariate/frank.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import scipy.integrate as integrate
from scipy.optimize import least_squares

from copulas import EPSILON
from copulas.bivariate.base import Bivariate, CopulaTypes
from copulas.bivariate.utils import split_matrix
from copulas.utils import EPSILON

MIN_FLOAT_LOG = np.log(sys.float_info.min)
MAX_FLOAT_LOG = np.log(sys.float_info.max)
Expand Down
2 changes: 1 addition & 1 deletion copulas/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pandas as pd
from scipy import stats

from copulas import set_random_state, validate_random_state
from copulas.utils import set_random_state, validate_random_state


def _dummy_fn(state):
Expand Down
5 changes: 5 additions & 0 deletions copulas/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Copulas Exceptions."""


class NotFittedError(Exception):
"""NotFittedError class."""
3 changes: 2 additions & 1 deletion copulas/multivariate/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import numpy as np

from copulas import NotFittedError, get_instance, validate_random_state
from copulas.errors import NotFittedError
from copulas.utils import get_instance, validate_random_state


class Multivariate(object):
Expand Down
Loading

0 comments on commit 74ac350

Please sign in to comment.