Source code for gemseo.mlearning.transformers.transformer

# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
# Contributors:
#    INITIAL AUTHORS - initial API and implementation and/or initial
#                         documentation
#        :author: Syver Doving Agdestein
#    OTHER AUTHORS   - MACROSCOPIC CHANGES
"""A transformer to apply operations on NumPy arrays.

The abstract :class:`.Transformer` class implements the concept of a data transformer.
Inheriting classes shall implement the :meth:`.Transformer.fit`,
:meth:`.Transformer.transform`
and possibly :meth:`.Transformer.inverse_transform` methods.

.. seealso::

   :mod:`~gemseo.mlearning.transformers.scaler.scaler`
   :mod:`~gemseo.mlearning.transformers.dimension_reduction.dimension_reduction`
"""

from __future__ import annotations

from abc import abstractmethod
from copy import deepcopy
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from typing import NoReturn
from typing import Union

from numpy import ndarray
from numpy import newaxis

from gemseo.core.base_factory import BaseFactory
from gemseo.utils.metaclasses import ABCGoogleDocstringInheritanceMeta

if TYPE_CHECKING:
    from collections.abc import Callable

    from typing_extensions import ParamSpecArgs
    from typing_extensions import ParamSpecKwargs

ParameterType = Union[bool, int, float, ndarray, str, None]
TransformerFitOptionType = Union[float, int, str]


[docs] class Transformer(metaclass=ABCGoogleDocstringInheritanceMeta): """A data transformer fitted from some samples.""" name: str """The name of the transformer.""" CROSSED: ClassVar[bool] = False """Whether the :meth:`.fit` method requires two data arrays.""" def __init__(self, name: str = "", **parameters: ParameterType) -> None: """ Args: name: A name for this transformer. **parameters: The parameters of the transformer. """ # noqa: D205 D212 self.name = name or self.__class__.__name__ self.__parameters = parameters self.__is_fitted = False @property def is_fitted(self) -> bool: """Whether the transformer has been fitted from some data.""" return self.__is_fitted @property def parameters(self) -> dict[str, ParameterType]: """The parameters of the transformer.""" return self.__parameters
[docs] def duplicate(self) -> Transformer: """Duplicate the current object. Returns: A deepcopy of the current instance. """ return deepcopy(self)
[docs] def fit(self, data: ndarray, *args: TransformerFitOptionType) -> None: """Fit the transformer to the data. Args: data: The data to be fitted, shaped as ``(n_observations, n_features)`` or ``(n_observations, )``. """ if data.ndim == 1: data = data[:, newaxis] self._fit(data, *args) self.__is_fitted = True
@abstractmethod def _fit(self, data: ndarray, *args: TransformerFitOptionType) -> None: """Fit the transformer to the data. Args: data: The data to be fitted, shaped as ``(n_observations, n_features)``. *args: The options for the transformer. """
[docs] @abstractmethod def transform(self, data: ndarray) -> ndarray: """Transform the data. Args: data: The data to be transformed, shaped as ``(n_observations, n_features)`` or ``(n_features, )``. Returns: The transformed data, shaped as ``data``. """
[docs] def inverse_transform(self, data: ndarray) -> NoReturn: """Perform an inverse transform on the data. Args: data: The data to be inverse transformed, shaped as ``(n_observations, n_features)`` or ``(n_features, )``. Returns: The inverse transformed data, shaped as ``data``. """ raise NotImplementedError
[docs] def fit_transform(self, data: ndarray, *args: TransformerFitOptionType) -> ndarray: """Fit the transformer to the data and transform the data. Args: data: The data to be transformed, shaped as ``(n_observations, n_features)`` or ``(n_observations, )``. Returns: The transformed data, shaped as ``data``. """ if data.ndim == 1: data = data[:, newaxis] self.fit(data, *args) return self.transform(data)
[docs] def compute_jacobian(self, data: ndarray) -> NoReturn: """Compute the Jacobian of :meth:`.transform`. Args: data: The data where the Jacobian is to be computed, shaped as ``(n_observations, n_features)`` or ``(n_features, )``. Returns: The Jacobian matrix, shaped according to ``data``. """ raise NotImplementedError
[docs] def compute_jacobian_inverse(self, data: ndarray) -> NoReturn: """Compute the Jacobian of the :meth:`.inverse_transform`. Args: data: The data where the Jacobian is to be computed, shaped as ``(n_observations, n_features)`` or ``(n_features, )``. Returns: The Jacobian matrix, shaped according to ``data``.. """ raise NotImplementedError
def __str__(self) -> str: return self.__class__.__name__ @staticmethod def _use_2d_array( f: Callable[[ndarray, ParamSpecArgs, ParamSpecKwargs], Any], ) -> Callable[[ndarray, ParamSpecArgs, ParamSpecKwargs], Any]: """Force the NumPy array passed to a function as first argument to be a 2D one. Args: f: The function. """ def g(self, data: ndarray, *args: Any, **kwargs: Any) -> Any: """Force a NumPy array to be 2D and evaluate the function ``f`` with it. Args: data: A 1D or 2D NumPy array. *args: The positional arguments. **kwargs: The optional arguments. Returns: Any kind of output; if a NumPy array, its dimension is made consistent with the shape of ``data``. """ if data.ndim == 2: return f(self, data, *args, **kwargs) out = f(self, data[newaxis, :], *args, **kwargs) if isinstance(out, ndarray): return out[0] return out return g
[docs] class TransformerFactory(BaseFactory): """A factory of :class:`.Transformer`.""" _CLASS = Transformer _MODULE_NAMES = ("gemseo.mlearning.transformers",)