From 4f4f332d9ef50a43d9b07d4a9bbc120c57c788c2 Mon Sep 17 00:00:00 2001 From: ElenaPetrunina Date: Mon, 28 Mar 2022 17:24:49 +0200 Subject: [PATCH 001/192] Smoothing in several dimensions Close #132 --- skfda/misc/hat_matrix.py | 74 +++++++++++-------- .../smoothing/_kernel_smoothers.py | 17 +++-- skfda/preprocessing/smoothing/_linear.py | 18 ++++- 3 files changed, 70 insertions(+), 39 deletions(-) diff --git a/skfda/misc/hat_matrix.py b/skfda/misc/hat_matrix.py index 82d53f444..25c086a0d 100644 --- a/skfda/misc/hat_matrix.py +++ b/skfda/misc/hat_matrix.py @@ -145,7 +145,7 @@ def _hat_matrix_function_not_normalized( if self.bandwidth is None: percentage = 15 - self.bandwidth = np.percentile(np.abs(delta_x), percentage) + self.bandwidth = np.percentile(delta_x, percentage) return self.kernel(delta_x / self.bandwidth) @@ -222,8 +222,8 @@ def __call__( # noqa: D102 self, *, delta_x: NDArrayFloat, - X_train: Optional[Union[FDataBasis, GridPoints]] = None, - X: Optional[Union[FDataBasis, GridPoints]] = None, + X_train: Optional[Union[FDataBasis, NDArrayFloat]] = None, + X: Optional[Union[FDataBasis, NDArrayFloat]] = None, y_train: Optional[NDArrayFloat] = None, weights: Optional[NDArrayFloat] = None, _cv: bool = False, @@ -231,21 +231,45 @@ def __call__( # noqa: D102 if self.bandwidth is None: percentage = 15 - self.bandwidth = np.percentile(np.abs(delta_x), percentage) + self.bandwidth = np.percentile(delta_x, percentage) - # Regression + # Smoothing for functions of one variable + if not isinstance(X_train, FDataBasis) and X_train[0].shape[0] == 1: + delta_x = np.subtract.outer( + X.flatten(), + X_train.flatten(), + ) + + return super().__call__( # noqa: WPS503 + delta_x=delta_x, + X_train=X_train, + X=X, + y_train=y_train, + weights=weights, + _cv=_cv, + ) + + # Regression and smoothing for multivariable functions if isinstance(X_train, FDataBasis): + m1 = X_train.coefficients + m2 = X.coefficients if y_train is None: y_train = np.identity(X_train.n_samples) - m1 = X_train.coefficients - m2 = X.coefficients + else: + m1 = X_train + m2 = X - # Subtract previous matrices obtaining a 3D matrix - # The i-th element contains the matrix X_train - X[i] - C = m1 - m2[:, np.newaxis] + if y_train is None: + y_train = np.identity(X_train.shape[0]) + # Subtract previous matrices obtaining a 3D matrix + # The i-th element contains the matrix X_train - X[i] + C = m1 - m2[:, np.newaxis] + + # Inner product matrix only is applicable in regression + if isinstance(X_train, FDataBasis): inner_product_matrix = X_train.basis.inner_product_matrix() # Calculate new coefficients taking into account cross-products @@ -256,27 +280,15 @@ def __call__( # noqa: D102 inner_product_matrix, ) - # Adding a column of ones in the first position of all matrices - dims = (C.shape[0], C.shape[1], 1) - C = np.c_[np.ones(dims), C] + # Adding a column of ones in the first position of all matrices + dims = (C.shape[0], C.shape[1], 1) + C = np.c_[np.ones(dims), C] - return self._solve_least_squares( - delta_x=delta_x, - coefs=C, - y_train=y_train, - ) - - # Smoothing - else: - - return super().__call__( # noqa: WPS503 - delta_x=delta_x, - X_train=X_train, - X=X, - y_train=y_train, - weights=weights, - _cv=_cv, - ) + return self._solve_least_squares( + delta_x=delta_x, + coefs=C, + y_train=y_train, + ) def _solve_least_squares( self, @@ -392,7 +404,7 @@ def _hat_matrix_function_not_normalized( # For each row in the distances matrix, it calculates the furthest # point within the k nearest neighbours vec = np.percentile( - np.abs(delta_x), + delta_x, self.n_neighbors / input_points_len * 100, axis=1, interpolation='lower', diff --git a/skfda/preprocessing/smoothing/_kernel_smoothers.py b/skfda/preprocessing/smoothing/_kernel_smoothers.py index 6dc7d82e3..0805b4a38 100644 --- a/skfda/preprocessing/smoothing/_kernel_smoothers.py +++ b/skfda/preprocessing/smoothing/_kernel_smoothers.py @@ -9,9 +9,10 @@ import numpy as np -from ..._utils._utils import _to_grid_points +from ..._utils._utils import _to_grid_points, _cartesian_product from ...misc.hat_matrix import HatMatrix, NadarayaWatsonHatMatrix -from ...representation._typing import GridPointsLike, NDArrayFloat +from ...misc.metrics import PairwiseMetric, l2_distance, Metric +from ...representation._typing import GridPointsLike, NDArrayFloat, Vector from ._linear import _LinearSmoother @@ -113,10 +114,12 @@ def __init__( kernel_estimator: Optional[HatMatrix] = None, weights: Optional[NDArrayFloat] = None, output_points: Optional[GridPointsLike] = None, + metric: Metric[Vector] = l2_distance, ): self.kernel_estimator = kernel_estimator self.weights = weights self.output_points = output_points + self.metric = metric self._cv = False # For testing purposes only def _hat_matrix( @@ -125,18 +128,18 @@ def _hat_matrix( output_points: GridPointsLike, ) -> NDArrayFloat: - input_points = _to_grid_points(input_points) - output_points = _to_grid_points(output_points) + input_points = _cartesian_product(_to_grid_points(input_points)) + output_points = _cartesian_product(_to_grid_points(output_points)) if self.kernel_estimator is None: self.kernel_estimator = NadarayaWatsonHatMatrix() - delta_x = np.subtract.outer(output_points[0], input_points[0]) + delta_x = PairwiseMetric(self.metric)(output_points, input_points) return self.kernel_estimator( delta_x=delta_x, weights=self.weights, - X_train=input_points, - X=output_points, + X_train=np.array(input_points), + X=np.array(output_points), _cv=self._cv, ) diff --git a/skfda/preprocessing/smoothing/_linear.py b/skfda/preprocessing/smoothing/_linear.py index f9d787d73..082872aa4 100644 --- a/skfda/preprocessing/smoothing/_linear.py +++ b/skfda/preprocessing/smoothing/_linear.py @@ -114,9 +114,25 @@ def transform( ) ) + dims = [len(e) for e in self.output_points_] + dims += [len(e) for e in self.input_points_] + + hat_matrix = np.reshape( + self.hat_matrix_, + (*dims,) + ) + + data_matrix = np.einsum( + hat_matrix, + [Ellipsis, *range(1, len(self.output_points_) + 1)], + X.data_matrix, + [0, *range(1, len(self.output_points_) + 2)], + [0, Ellipsis, len(self.output_points_) + 1], + ) + # The matrix is cached return X.copy( - data_matrix=self.hat_matrix_ @ X.data_matrix, + data_matrix=data_matrix, grid_points=self.output_points_, ) From a80f779f9590b29d650697945fdc8caf541c0813 Mon Sep 17 00:00:00 2001 From: ElenaPetrunina Date: Mon, 28 Mar 2022 17:40:42 +0200 Subject: [PATCH 002/192] Fix code style --- skfda/misc/hat_matrix.py | 2 +- skfda/preprocessing/smoothing/_kernel_smoothers.py | 4 ++-- skfda/preprocessing/smoothing/_linear.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/skfda/misc/hat_matrix.py b/skfda/misc/hat_matrix.py index 25c086a0d..5a564117f 100644 --- a/skfda/misc/hat_matrix.py +++ b/skfda/misc/hat_matrix.py @@ -15,7 +15,7 @@ from sklearn.base import BaseEstimator, RegressorMixin from ..representation._functional_data import FData -from ..representation._typing import GridPoints, GridPointsLike, NDArrayFloat +from ..representation._typing import GridPointsLike, NDArrayFloat from ..representation.basis import FDataBasis from . import kernels diff --git a/skfda/preprocessing/smoothing/_kernel_smoothers.py b/skfda/preprocessing/smoothing/_kernel_smoothers.py index 0805b4a38..1d3f2c262 100644 --- a/skfda/preprocessing/smoothing/_kernel_smoothers.py +++ b/skfda/preprocessing/smoothing/_kernel_smoothers.py @@ -9,9 +9,9 @@ import numpy as np -from ..._utils._utils import _to_grid_points, _cartesian_product +from ..._utils._utils import _cartesian_product, _to_grid_points from ...misc.hat_matrix import HatMatrix, NadarayaWatsonHatMatrix -from ...misc.metrics import PairwiseMetric, l2_distance, Metric +from ...misc.metrics import Metric, PairwiseMetric, l2_distance from ...representation._typing import GridPointsLike, NDArrayFloat, Vector from ._linear import _LinearSmoother diff --git a/skfda/preprocessing/smoothing/_linear.py b/skfda/preprocessing/smoothing/_linear.py index 082872aa4..4f1ebf626 100644 --- a/skfda/preprocessing/smoothing/_linear.py +++ b/skfda/preprocessing/smoothing/_linear.py @@ -119,7 +119,7 @@ def transform( hat_matrix = np.reshape( self.hat_matrix_, - (*dims,) + dims, ) data_matrix = np.einsum( From 1a58019338a33c84049e35cbdada58c37f3fb43f Mon Sep 17 00:00:00 2001 From: ElenaPetrunina Date: Wed, 30 Mar 2022 13:06:47 +0200 Subject: [PATCH 003/192] Update hat_matrix.py --- skfda/misc/hat_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/misc/hat_matrix.py b/skfda/misc/hat_matrix.py index ffb7dc3fe..d280a03ff 100644 --- a/skfda/misc/hat_matrix.py +++ b/skfda/misc/hat_matrix.py @@ -281,7 +281,7 @@ def __call__( # noqa: D102 # Calculate new coefficients taking into account cross-products # if the basis is orthonormal, C would not change C = C @ inner_product_matrix # noqa: WPS350 - + # Adding a column of ones in the first position of all matrices dims = (C.shape[0], C.shape[1], 1) C = np.concatenate((np.ones(dims), C), axis=-1) From 168fad10fea71375345975197b1b25452ada94d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Wed, 11 Jan 2023 00:11:43 +0100 Subject: [PATCH 004/192] Covariance function as tensor product --- skfda/representation/basis/_fdatabasis.py | 37 +++++++++++++++-------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 8b44310be..5f9d770fe 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -22,6 +22,7 @@ from ...typing._numpy import ArrayLike, NDArrayBool, NDArrayFloat, NDArrayInt from .. import grid from .._functional_data import FData +from ..basis import TensorBasis from ..extrapolation import ExtrapolationLike if TYPE_CHECKING: @@ -461,25 +462,35 @@ def var(self: T, eval_points: Optional[NDArrayFloat] = None) -> T: """ return self.to_grid(eval_points).var().to_basis(self.basis) - def cov(self, eval_points: Optional[NDArrayFloat] = None) -> FData: + def cov(self: T) -> T: """Compute the covariance of the functional data object. - A numerical approach its used. The object its transformed into its - discrete representation and then the covariance matrix is computed. - - Args: - eval_points: Set of points where the - functions are evaluated to obtain the discrete - representation of the object. If none are passed it calls - numpy.linspace with bounds equal to the ones defined in - self.domain_range and the number of points the maximum - between 501 and 10 times the number of basis. + Calculates the unbiased sample covariance function of the data. + This is a function defined over the basis consisting of the tensor + product of the original basis with itself. The resulting covariance + function is then represented as an FDataBasis object with one + sample with such tensor basis with the coefficients being the + flattened covariance matrix of the original coefficients. Returns: - Matrix of covariances. + Covariance function in the tensor basis. """ - return self.to_grid(eval_points).cov() + dataset_name = ( + f"{self.dataset_name} - covariance" + if self.dataset_name is not None else None + ) + + basis = TensorBasis([self.basis, self.basis]) + coefficients = np.cov(self.coefficients, rowvar=False).flatten() + + return FDataBasis( + basis=basis, + coefficients=coefficients, + dataset_name=dataset_name, + argument_names=self.argument_names * 2, + sample_names=("covariance",), + ) def to_grid( self, From 8ce5cef613f503c30d4fbf19cebd587939b520a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Wed, 11 Jan 2023 00:54:04 +0100 Subject: [PATCH 005/192] Convert covariance to grid in order to use data_matrix --- skfda/inference/anova/_anova_oneway.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/skfda/inference/anova/_anova_oneway.py b/skfda/inference/anova/_anova_oneway.py index be30d3417..858e4b6ec 100644 --- a/skfda/inference/anova/_anova_oneway.py +++ b/skfda/inference/anova/_anova_oneway.py @@ -209,11 +209,14 @@ def _anova_bootstrap( random_state = validate_random_state(random_state) if equal_var: - k_est = concatenate(fd_grouped).cov().data_matrix[0, ..., 0] + k_est = concatenate(fd_grouped).cov().to_grid().data_matrix[0, ..., 0] k_est = [k_est] * len(fd_grouped) else: # Estimating covariances for each group - k_est = [fd.cov().data_matrix[0, ..., 0] for fd in fd_grouped] + k_est = [ + fd.cov().to_grid().data_matrix[0, ..., 0] + for fd in fd_grouped + ] # Simulating n_reps observations for each of the n_groups gaussian # processes From 16dd136561fea3b665820ebd78120aa23b71e623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Wed, 11 Jan 2023 01:31:36 +0100 Subject: [PATCH 006/192] Reorder imports to avoid circular dependency --- skfda/representation/basis/_fdatabasis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 5f9d770fe..b1b8ebdb5 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -22,8 +22,8 @@ from ...typing._numpy import ArrayLike, NDArrayBool, NDArrayFloat, NDArrayInt from .. import grid from .._functional_data import FData -from ..basis import TensorBasis from ..extrapolation import ExtrapolationLike +from . import TensorBasis if TYPE_CHECKING: from .. import FDataGrid From 2f8078834baf005a55a78c1cd8ac45738903cf61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Wed, 11 Jan 2023 01:36:44 +0100 Subject: [PATCH 007/192] Reorder imports to avoid circular dependency --- skfda/representation/basis/_fdatabasis.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index b1b8ebdb5..7e072169b 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -23,11 +23,10 @@ from .. import grid from .._functional_data import FData from ..extrapolation import ExtrapolationLike -from . import TensorBasis if TYPE_CHECKING: from .. import FDataGrid - from . import Basis + from . import Basis, TensorBasis T = TypeVar('T', bound='FDataBasis') From 92d20f1653db13007f5f6a9fa79b68709846aad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Wed, 11 Jan 2023 01:45:33 +0100 Subject: [PATCH 008/192] Reorder imports to avoid circular dependency --- skfda/representation/basis/_fdatabasis.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 7e072169b..17c816dc5 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -26,7 +26,7 @@ if TYPE_CHECKING: from .. import FDataGrid - from . import Basis, TensorBasis + from . import Basis T = TypeVar('T', bound='FDataBasis') @@ -475,6 +475,9 @@ def cov(self: T) -> T: Covariance function in the tensor basis. """ + # import TensorBasis here to avoid circular dependencies + from . import TensorBasis + dataset_name = ( f"{self.dataset_name} - covariance" if self.dataset_name is not None else None From e6afec7edfa48ed8e6ed0f73bed7100a4ef0f664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Sat, 14 Jan 2023 19:18:52 +0100 Subject: [PATCH 009/192] Abstract cov method for FData --- skfda/representation/_functional_data.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index 9317d0400..887dcd706 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -819,6 +819,20 @@ def sum( # noqa: WPS125 return self + @abstractmethod + def cov(self: T) -> T: + """Compute the covariance of the functional data object. + + Calculates the unbiased sample covariance function of the data. + This is expected to be only defined for univariate functions. + The resulting covariance function is defined in the cartesian + product of the domain of the functions. + + Returns: + Covariance function. + """ + pass + def mean( self: T, *, From 9f15f788047e8c32494384700f6a2130c0e7a7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Sat, 21 Jan 2023 17:47:21 +0100 Subject: [PATCH 010/192] Test behaviour of covariance for FDataBasis --- skfda/tests/test_fdata_cov.py | 197 ++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 skfda/tests/test_fdata_cov.py diff --git a/skfda/tests/test_fdata_cov.py b/skfda/tests/test_fdata_cov.py new file mode 100644 index 000000000..6a455a816 --- /dev/null +++ b/skfda/tests/test_fdata_cov.py @@ -0,0 +1,197 @@ +"""Test the covariance method of FData.""" +import random +from typing import Any, Callable, Tuple + +import numpy as np +import pytest + +from skfda.datasets import make_gaussian_process +from skfda.misc.covariances import CovarianceLike, Gaussian +from skfda.representation import FDataBasis +from skfda.representation.basis import Basis, BSplineBasis, FourierBasis + +############ +# FIXTURES +############ + + +@pytest.fixture( + params=[ + FourierBasis, + BSplineBasis, + ], + ids=[ + "FourierBasis", + "BSplineBasis", + ], +) +def basis_type( + request: Any, +) -> Any: + """Fixture for classes to test.""" + return request.param + + +@pytest.fixture( + params=[ + 5, + 30, + ], +) +def n_basis( + request: Any, +) -> Any: + """Generate a basis.""" + return request.param + + +@pytest.fixture( + params=[ + 100, + ], +) +def n_samples( + request: Any, +) -> Any: + """Generate number of sample.""" + return request.param + + +@pytest.fixture( + params=[ + 100, + ], +) +def n_features( + request: Any, +) -> Any: + """Generate number of features (points of evaluation).""" + return request.param + + +@pytest.fixture( + params=[ + (0.0, 1.0), + (-2.0, 2.0), + ], +) +def interval( + request: Any, +) -> Any: + """Generate an interval.""" + return request.param + + +@pytest.fixture( + params=[ + 0.0, + 1.0, + ], +) +def mean( + request: Any, +) -> Any: + """Generate a mean.""" + return request.param + + +@pytest.fixture( + params=[ + 0.1, + ], +) +def noise( + request: Any, +) -> Any: + """Generate a noise value.""" + return request.param + + +@pytest.fixture( + params=[ + Gaussian( + variance=1, + length_scale=1, + ), + Gaussian( + variance=0.1, + length_scale=1, + ), + Gaussian( + variance=2, + length_scale=0.5, + ), + ], +) +def covariance( + request: Any, +) -> Any: + """Generate a covariance kernel.""" + return request.param + + +@pytest.fixture +def data_in_basis( + basis_type: Callable[..., Basis], + n_basis: int, + n_samples: int, + n_features: int, + interval: Tuple[float, float], + mean: float, + noise: float, + covariance: CovarianceLike, +) -> FDataBasis: + """Generate gaussian process data using a basis.""" + basis = basis_type( + n_basis=n_basis, + domain_range=interval, + ) + return make_gaussian_process( + n_samples=n_samples, + n_features=n_features, + start=interval[0], + stop=interval[1], + mean=mean, + cov=covariance, + noise=noise, + ).to_basis(basis) + + +############ +# TESTS +############ + +def test_fdatabasis_covariance( + data_in_basis: FDataBasis, +) -> None: + """Test the covariance method of FDataBasis. + + The resulting covariance function is defined on the tensor basis that is + the tensor product of the basis of the original functions with itself. + + This test checks that the covariance function calculated for FDataBasis + objects is equal to the covariance function calculated for the grid + representation of the same data. + """ + data_in_grid = data_in_basis.to_grid() + + cov_basis = data_in_basis.cov() + cov_grid = data_in_grid.cov() + + grid_points = cov_grid.grid_points + + # Select a random sample of evaluation points + x_coordinates = random.sample(list(grid_points[0]), 10) + y_coordinates = random.sample(list(grid_points[1]), 10) + evaluation_points = np.array([ + [x, y] for x in x_coordinates for y in y_coordinates + ]) + + # Check that the domain range is the same + np.testing.assert_equal(cov_grid.domain_range, cov_basis.domain_range) + + # Check that the covariance functions are equal + np.testing.assert_allclose( + cov_basis(evaluation_points), + cov_grid(evaluation_points), + ) From b95858eb378d084fb3e84f23fa0f2dacdfbe9a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Thu, 26 Jan 2023 01:22:36 +0100 Subject: [PATCH 011/192] Overload cov method to accept s and t evaluation points --- skfda/representation/_functional_data.py | 30 ++++++++++++++++- skfda/representation/basis/_fdatabasis.py | 40 +++++++++++++++++++++-- skfda/representation/grid.py | 33 +++++++++++++++++-- skfda/tests/test_fdata_cov.py | 27 +++++---------- 4 files changed, 106 insertions(+), 24 deletions(-) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index 887dcd706..d0da95bbc 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -819,17 +819,45 @@ def sum( # noqa: WPS125 return self + @overload + def cov( + self: T, + s_points: NDArrayFloat, + t_points: NDArrayFloat, + ) -> NDArrayFloat: + pass + + @overload + def cov( + self: T, + ) -> T: + pass + @abstractmethod - def cov(self: T) -> T: + def cov( + self: T, + s_points: Optional[NDArrayFloat] = None, + t_points: Optional[NDArrayFloat] = None, + ) -> Union[NDArrayFloat, T]: """Compute the covariance of the functional data object. Calculates the unbiased sample covariance function of the data. This is expected to be only defined for univariate functions. The resulting covariance function is defined in the cartesian product of the domain of the functions. + If s_points and t_points are not provided, this method returns + the FData object representing the covariance function. + If s_points and t_points are provided, this method returns the + evaluation of the covariance function at the grid formed by the + cartesian product of the points in s_points and t_points. + + Args: + s_points: Points where the covariance function is evaluated. + t_points: Points where the covariance function is evaluated. Returns: Covariance function. + """ pass diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 17c816dc5..94c4fc81f 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -12,6 +12,7 @@ TypeVar, Union, cast, + overload, ) import numpy as np @@ -461,18 +462,47 @@ def var(self: T, eval_points: Optional[NDArrayFloat] = None) -> T: """ return self.to_grid(eval_points).var().to_basis(self.basis) - def cov(self: T) -> T: + @overload + def cov( + self: T, + s_points: NDArrayFloat, + t_points: NDArrayFloat, + ) -> NDArrayFloat: + pass + + @overload + def cov( + self: T, + ) -> T: + pass + + def cov( + self: T, + s_points: Optional[NDArrayFloat] = None, + t_points: Optional[NDArrayFloat] = None, + ) -> Union[T, NDArrayFloat]: """Compute the covariance of the functional data object. Calculates the unbiased sample covariance function of the data. + This is expected to be only defined for univariate functions. This is a function defined over the basis consisting of the tensor product of the original basis with itself. The resulting covariance function is then represented as an FDataBasis object with one sample with such tensor basis with the coefficients being the flattened covariance matrix of the original coefficients. + If s_points and t_points are not provided, this method returns + the FData object representing the covariance function. + If s_points and t_points are provided, this method returns the + evaluation of the covariance function at the grid formed by the + cartesian product of the points in s_points and t_points. + + Args: + s_points: Points where the covariance function is evaluated. + t_points: Points where the covariance function is evaluated. + Returns: - Covariance function in the tensor basis. + Covariance function. """ # import TensorBasis here to avoid circular dependencies @@ -486,7 +516,7 @@ def cov(self: T) -> T: basis = TensorBasis([self.basis, self.basis]) coefficients = np.cov(self.coefficients, rowvar=False).flatten() - return FDataBasis( + covariance_function = FDataBasis( basis=basis, coefficients=coefficients, dataset_name=dataset_name, @@ -494,6 +524,10 @@ def cov(self: T) -> T: sample_names=("covariance",), ) + if s_points is not None and t_points is not None: + return covariance_function([s_points, t_points], grid=True) + return covariance_function + def to_grid( self, grid_points: Optional[GridPointsLike] = None, diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index b54a6f19c..68bc2271e 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -19,6 +19,7 @@ TypeVar, Union, cast, + overload, ) import findiff @@ -593,11 +594,36 @@ def var(self: T) -> T: sample_names=("variance",), ) - def cov(self: T) -> T: + @overload + def cov( + self: T, + s_points: NDArrayFloat, + t_points: NDArrayFloat, + ) -> NDArrayFloat: + pass + + @overload + def cov( + self: T, + ) -> T: + pass + + def cov( + self: T, + s_points: Optional[NDArrayFloat] = None, + t_points: Optional[NDArrayFloat] = None, + ) -> Union[T, NDArrayFloat]: """Compute the covariance. Calculates the covariance matrix representing the covariance of the functional samples at the observation points. + if s_points and t_points are both specified, this method returns the + covariance function evaluated at the grid points formed by the + cartesian product of s_points and t_points. + + Args: + s_points: Grid points where the covariance function is evaluated. + t_points: Grid points where the covariance function is evaluated. Returns: Covariance function. @@ -614,7 +640,7 @@ def cov(self: T) -> T: "for univariate functions", ) - return self.copy( + covariance_function = self.copy( data_matrix=np.cov( self.data_matrix[..., 0], rowvar=False, @@ -631,6 +657,9 @@ def cov(self: T) -> T: argument_names=self.argument_names * 2, sample_names=("covariance",), ) + if s_points is not None and t_points is not None: + return covariance_function([s_points, t_points], grid=True) + return covariance_function def gmean(self: T) -> T: """Compute the geometric mean of all samples in the FDataGrid object. diff --git a/skfda/tests/test_fdata_cov.py b/skfda/tests/test_fdata_cov.py index 6a455a816..d3cc937c4 100644 --- a/skfda/tests/test_fdata_cov.py +++ b/skfda/tests/test_fdata_cov.py @@ -1,5 +1,4 @@ """Test the covariance method of FData.""" -import random from typing import Any, Callable, Tuple import numpy as np @@ -173,25 +172,17 @@ def test_fdatabasis_covariance( objects is equal to the covariance function calculated for the grid representation of the same data. """ - data_in_grid = data_in_basis.to_grid() + # Select grid points before converting to grid + # Rename evaluation points as (s, t) + # Use grid=True to get evaluation matrix using two parameters. + domain_range = data_in_basis.domain_range[0] + grid_points = np.linspace(domain_range[0], domain_range[1], 100) + data_in_grid = data_in_basis.to_grid(grid_points) - cov_basis = data_in_basis.cov() - cov_grid = data_in_grid.cov() - - grid_points = cov_grid.grid_points - - # Select a random sample of evaluation points - x_coordinates = random.sample(list(grid_points[0]), 10) - y_coordinates = random.sample(list(grid_points[1]), 10) - evaluation_points = np.array([ - [x, y] for x in x_coordinates for y in y_coordinates - ]) - - # Check that the domain range is the same - np.testing.assert_equal(cov_grid.domain_range, cov_basis.domain_range) + s_grid, t_grid = grid_points, grid_points # Check that the covariance functions are equal np.testing.assert_allclose( - cov_basis(evaluation_points), - cov_grid(evaluation_points), + data_in_basis.cov(s_grid, t_grid), + data_in_grid.cov(s_grid, t_grid), ) From 2ed172c9f7b4dc6da494be08bde1ad1dd3dbf2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Thu, 26 Jan 2023 01:39:29 +0100 Subject: [PATCH 012/192] Rename control variable to avoid typing warnings --- skfda/inference/anova/_anova_oneway.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skfda/inference/anova/_anova_oneway.py b/skfda/inference/anova/_anova_oneway.py index 858e4b6ec..81db74c28 100644 --- a/skfda/inference/anova/_anova_oneway.py +++ b/skfda/inference/anova/_anova_oneway.py @@ -214,8 +214,8 @@ def _anova_bootstrap( else: # Estimating covariances for each group k_est = [ - fd.cov().to_grid().data_matrix[0, ..., 0] - for fd in fd_grouped + fdg.cov().to_grid().data_matrix[0, ..., 0] + for fdg in fd_grouped ] # Simulating n_reps observations for each of the n_groups gaussian From 3e74af5572d319e98a7184d934f2df2f456b95c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Mon, 30 Jan 2023 00:25:14 +0100 Subject: [PATCH 013/192] Evaluate assuming covariance is univariate --- skfda/representation/basis/_fdatabasis.py | 5 ++++- skfda/representation/grid.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 94c4fc81f..378858c69 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -525,7 +525,10 @@ def cov( ) if s_points is not None and t_points is not None: - return covariance_function([s_points, t_points], grid=True) + return covariance_function( + [s_points, t_points], + grid=True, + )[0, ..., 0] return covariance_function def to_grid( diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 68bc2271e..25f33cba8 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -658,7 +658,10 @@ def cov( sample_names=("covariance",), ) if s_points is not None and t_points is not None: - return covariance_function([s_points, t_points], grid=True) + return covariance_function( + [s_points, t_points], + grid=True, + )[0, ..., 0] return covariance_function def gmean(self: T) -> T: From 2d644636d284c4fbcbc208a8a6828362daa4926b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Mon, 30 Jan 2023 00:40:36 +0100 Subject: [PATCH 014/192] Fix comments --- skfda/tests/test_fdata_cov.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/skfda/tests/test_fdata_cov.py b/skfda/tests/test_fdata_cov.py index d3cc937c4..1db984771 100644 --- a/skfda/tests/test_fdata_cov.py +++ b/skfda/tests/test_fdata_cov.py @@ -173,8 +173,6 @@ def test_fdatabasis_covariance( representation of the same data. """ # Select grid points before converting to grid - # Rename evaluation points as (s, t) - # Use grid=True to get evaluation matrix using two parameters. domain_range = data_in_basis.domain_range[0] grid_points = np.linspace(domain_range[0], domain_range[1], 100) data_in_grid = data_in_basis.to_grid(grid_points) From 16dfe11fb1a79b219964809f9c297d750a53f770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Mon, 30 Jan 2023 12:02:21 +0100 Subject: [PATCH 015/192] Refactor test --- skfda/tests/test_fdata_cov.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skfda/tests/test_fdata_cov.py b/skfda/tests/test_fdata_cov.py index 1db984771..db1af3ca9 100644 --- a/skfda/tests/test_fdata_cov.py +++ b/skfda/tests/test_fdata_cov.py @@ -84,7 +84,6 @@ def interval( @pytest.fixture( params=[ 0.0, - 1.0, ], ) def mean( @@ -117,7 +116,7 @@ def noise( length_scale=1, ), Gaussian( - variance=2, + variance=3, length_scale=0.5, ), ], @@ -162,6 +161,7 @@ def data_in_basis( def test_fdatabasis_covariance( data_in_basis: FDataBasis, + n_features: int, ) -> None: """Test the covariance method of FDataBasis. @@ -174,7 +174,7 @@ def test_fdatabasis_covariance( """ # Select grid points before converting to grid domain_range = data_in_basis.domain_range[0] - grid_points = np.linspace(domain_range[0], domain_range[1], 100) + grid_points = np.linspace(domain_range[0], domain_range[1], n_features) data_in_grid = data_in_basis.to_grid(grid_points) s_grid, t_grid = grid_points, grid_points From 84c8577d70ba830a7d2238a384dc3936d246a5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Mon, 30 Jan 2023 12:03:36 +0100 Subject: [PATCH 016/192] Use evaluation in cov directly --- skfda/inference/anova/_anova_oneway.py | 31 +++++++++++++++---------- skfda/inference/hotelling/_hotelling.py | 16 +++++++++---- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/skfda/inference/anova/_anova_oneway.py b/skfda/inference/anova/_anova_oneway.py index 81db74c28..2e16b5afc 100644 --- a/skfda/inference/anova/_anova_oneway.py +++ b/skfda/inference/anova/_anova_oneway.py @@ -190,6 +190,7 @@ def _anova_bootstrap( random_state: RandomStateLike = None, p: int = 2, equal_var: bool = True, + default_n_features: int = 501, ) -> NDArrayFloat: n_groups = len(fd_grouped) @@ -203,29 +204,35 @@ def _anova_bootstrap( ) # List with sizes of each group - sizes = [fd.n_samples for fd in fd_grouped] + sizes = [fdg.n_samples for fdg in fd_grouped] # Instance a random state object in case random_state is an int random_state = validate_random_state(random_state) - if equal_var: - k_est = concatenate(fd_grouped).cov().to_grid().data_matrix[0, ..., 0] - k_est = [k_est] * len(fd_grouped) - else: - # Estimating covariances for each group - k_est = [ - fdg.cov().to_grid().data_matrix[0, ..., 0] - for fdg in fd_grouped - ] - # Simulating n_reps observations for each of the n_groups gaussian # processes grid_points = getattr(fd_grouped[0], "grid_points", None) if grid_points is None: start, stop = fd_grouped[0].domain_range[0] - n_features = k_est[0].shape[0] + n_features = default_n_features grid_points = np.linspace(start, stop, n_features) + if equal_var: + cov_est = concatenate(fd_grouped).cov( + s_points=grid_points, + t_points=grid_points, + ) + k_est = [cov_est] * len(fd_grouped) + else: + # Estimating covariances for each group + k_est = [ + fdg.cov( + s_points=grid_points, + t_points=grid_points, + ) + for fdg in fd_grouped + ] + sim = [ make_gaussian( n_reps, diff --git a/skfda/inference/hotelling/_hotelling.py b/skfda/inference/hotelling/_hotelling.py index 3580d0b42..b154840d7 100644 --- a/skfda/inference/hotelling/_hotelling.py +++ b/skfda/inference/hotelling/_hotelling.py @@ -8,7 +8,7 @@ from typing_extensions import Literal from ...misc.validation import validate_random_state -from ...representation import FData, FDataBasis +from ...representation import FData, FDataBasis, FDataGrid from ...typing._base import RandomStateLike from ...typing._numpy import NDArrayFloat @@ -85,7 +85,7 @@ def hotelling_t2( n = n1 + n2 # Size of full sample m = fd1.mean() - fd2.mean() # Delta mean - if isinstance(fd1, FDataBasis): + if isinstance(fd1, FDataBasis) and isinstance(fd2, FDataBasis): if fd1.basis != fd2.basis: raise ValueError( "Both FDataBasis objects must share the same basis.", @@ -97,11 +97,17 @@ def hotelling_t2( # If no weight matrix is passed, then we compute the Gram Matrix weights = fd1.basis.gram_matrix() weights = np.sqrt(weights) - else: + elif isinstance(fd1, FDataGrid) and isinstance(fd2, FDataGrid): # Working with standard discretized data m = m.data_matrix[0, ..., 0] - k1 = fd1.cov().data_matrix[0, ..., 0] - k2 = fd2.cov().data_matrix[0, ..., 0] + k1 = fd1.cov( + s_points=fd1.grid_points[0], + t_points=fd1.grid_points[0], + ) + k2 = fd2.cov( + s_points=fd2.grid_points[0], + t_points=fd2.grid_points[0], + ) m = m.reshape((-1, 1)) # Reshaping the mean for a proper matrix product k_pool = ((n1 - 1) * k1 + (n2 - 1) * k2) / (n - 2) # Combination of covs From 14899f5009a4329cf3cd6a45281f2b36a80d7e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Mon, 30 Jan 2023 12:04:13 +0100 Subject: [PATCH 017/192] Fix cov typing --- skfda/exploratory/stats/_stats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index ac89a992b..ccea10129 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -71,7 +71,7 @@ def gmean(X: FDataGrid) -> FDataGrid: return X.gmean() -def cov(X: FData) -> FDataGrid: +def cov(X: F) -> F: """ Compute the covariance. @@ -86,7 +86,7 @@ def cov(X: FData) -> FDataGrid: :term:`functional data object` with just one sample. """ - return X.cov() # type: ignore[no-any-return] + return X.cov() def modified_epigraph_index(X: FDataGrid) -> NDArrayFloat: From 58c88e7bfc52d583c6b54a74560038f9805e1f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Wed, 22 Feb 2023 21:22:03 +0100 Subject: [PATCH 018/192] Temporal fix: discretize in case of missing grid_points using fine mesh constant --- skfda/inference/anova/_anova_oneway.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skfda/inference/anova/_anova_oneway.py b/skfda/inference/anova/_anova_oneway.py index 2e16b5afc..94a78201f 100644 --- a/skfda/inference/anova/_anova_oneway.py +++ b/skfda/inference/anova/_anova_oneway.py @@ -5,6 +5,7 @@ import numpy as np from typing_extensions import Literal +from ..._utils import constants from ...datasets import make_gaussian from ...misc.metrics import lp_distance from ...misc.validation import validate_random_state @@ -190,7 +191,7 @@ def _anova_bootstrap( random_state: RandomStateLike = None, p: int = 2, equal_var: bool = True, - default_n_features: int = 501, + default_n_features: int = constants.N_POINTS_FINE_MESH, ) -> NDArrayFloat: n_groups = len(fd_grouped) From c21d72ad5aaac3a8a3d83d63b1dc05726412e8e7 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sat, 4 Feb 2023 20:21:59 +0100 Subject: [PATCH 019/192] Usable up to 2000 points --- .../_linear_differential_operator.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index b0ebb5614..cfc3b3635 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -608,17 +608,34 @@ def fdatagrid_penalty_matrix_optimized( assert not isinstance(evaluated_basis, int) indices = np.triu_indices(basis.n_samples) - product = evaluated_basis[indices[0]] * evaluated_basis[indices[1]] + x_indeces = indices[0] + y_indeces = indices[1] + + # Only compute the values for the points that are close enough. + # This is done to avoid computing the integral for the product of + # two fuctions with a disjoined support. + indices_difference = np.abs(x_indeces - y_indeces).astype(int) + + # The support of the result of applying the linear operator + # can be at most the order of the linear operator plus one. + # Therefore, if the difference between the indeces is greater + # twice the order of the linear operator plus one, the two + # functions are disjoined. + spread = 2 * (len(linear_operator.weights) + 1) + x_indeces = x_indeces[indices_difference < spread] + y_indeces = y_indeces[indices_difference < spread] + + product = evaluated_basis[x_indeces] * evaluated_basis[y_indeces] triang_vec = scipy.integrate.simps(product[..., 0], x=basis.grid_points) - matrix = np.empty((basis.n_samples, basis.n_samples)) + matrix = np.zeros((basis.n_samples, basis.n_samples)) # Set upper matrix - matrix[indices] = triang_vec + matrix[(x_indeces, y_indeces)] = triang_vec.flatten() # Set lower matrix - matrix[(indices[1], indices[0])] = triang_vec + matrix[(y_indeces, x_indeces)] = triang_vec.flatten() return matrix From 258027d0764b544807af1c0482816c7ccbb8828a Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 5 Feb 2023 00:01:01 +0100 Subject: [PATCH 020/192] Regularization usable up to 10 000 points. From this point on, memory usage might start to be a problem. Thus, it does not make sense to optimize the running time of the algoritm much more. --- .../_linear_differential_operator.py | 119 +++++++++++++----- 1 file changed, 91 insertions(+), 28 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index cfc3b3635..280fdf6a3 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -3,6 +3,7 @@ import numbers from typing import Callable, Sequence, Union +import findiff import numpy as np import scipy.integrate from numpy import polyder, polyint, polymul, polyval @@ -577,6 +578,40 @@ def bspline_penalty_matrix_optimized( return penalty_matrix +def _optimized_operator_evaluation_in_grid( + linear_operator: LinearDifferentialOperator, + grid_points: NDArrayFloat, +) -> NDArrayFloat: + n_points = grid_points.shape[0] + diff_coefficients = np.zeros((n_points, n_points)) + for dif_order, w in enumerate(linear_operator.weights): + if w == 0: + continue + for point_index in range(n_points): + finate_dif = findiff.coefs.coefficients_non_uni( + deriv=dif_order, + acc=2, + coords=grid_points, + idx=point_index, + ) + # The offsets are the relative positions of the + # points where each coefficient is applied + offsets = finate_dif["offsets"] + coefficients = finate_dif["coefficients"] + + # Add the position of the point where the finite + # difference is calculated to get the absolute position + # of the points where each coefficient is applied + offsets += point_index + if callable(w): + coefficients *= w(grid_points[offsets]) + diff_coefficients[point_index, offsets] += coefficients + else: + diff_coefficients[point_index, offsets] += w * coefficients + + return diff_coefficients.T + + @gram_matrix_optimization.register def fdatagrid_penalty_matrix_optimized( linear_operator: LinearDifferentialOperator, @@ -600,21 +635,10 @@ def fdatagrid_penalty_matrix_optimized( Note that the first case is a particular case of the second one. """ - evaluated_basis = sum( - w(basis.grid_points[0]) if callable(w) else w - * basis.derivative(order=i)(basis.grid_points[0]) - for i, w in enumerate(linear_operator.weights) + evaluated_basis = _optimized_operator_evaluation_in_grid( + linear_operator, + basis.grid_points[0], ) - assert not isinstance(evaluated_basis, int) - - indices = np.triu_indices(basis.n_samples) - x_indeces = indices[0] - y_indeces = indices[1] - - # Only compute the values for the points that are close enough. - # This is done to avoid computing the integral for the product of - # two fuctions with a disjoined support. - indices_difference = np.abs(x_indeces - y_indeces).astype(int) # The support of the result of applying the linear operator # can be at most the order of the linear operator plus one. @@ -622,22 +646,44 @@ def fdatagrid_penalty_matrix_optimized( # twice the order of the linear operator plus one, the two # functions are disjoined. spread = 2 * (len(linear_operator.weights) + 1) - x_indeces = x_indeces[indices_difference < spread] - y_indeces = y_indeces[indices_difference < spread] - - product = evaluated_basis[x_indeces] * evaluated_basis[y_indeces] - - triang_vec = scipy.integrate.simps(product[..., 0], x=basis.grid_points) - matrix = np.zeros((basis.n_samples, basis.n_samples)) - - # Set upper matrix - matrix[(x_indeces, y_indeces)] = triang_vec.flatten() - - # Set lower matrix - matrix[(y_indeces, x_indeces)] = triang_vec.flatten() + # Add a border to avoid problems with the integration weights + # at the edges of the interval + border_distance = 5 + + n_points = basis.grid_points[0].shape[0] + product_matrix = np.zeros((basis.n_samples, basis.n_samples)) + for i in range(n_points): + for j in range(i, min(i + spread, n_points)): + left_lim = max(0, i - spread - border_distance) + right_lim = min(n_points, j + spread + border_distance) + + # Simpson's rule requires an even number of intervals. + # If that is not the case, scipy applies a correction + # (see simpson docstring). This correction can lead to + # different results when the interval of integration is + # changed. To avoid this, we add a point to the left or + # to the right of the interval. + if (right_lim - left_lim) % 2 == 1: + if right_lim < n_points: + right_lim += 1 + elif left_lim > 0: + left_lim -= 1 + + # Limit both the multiplication and the integration to + # the interval where the functions are not 0. + operator_product = ( + evaluated_basis[i, left_lim:right_lim] + * evaluated_basis[j, left_lim:right_lim] + ) + sub_points = basis.grid_points[0][left_lim:right_lim] + product_matrix[i, j] = scipy.integrate.simpson( + operator_product, + sub_points, + ) + product_matrix[j, i] = product_matrix[i, j] - return matrix + return product_matrix @gram_matrix_optimization.register @@ -667,4 +713,21 @@ def custombasis_penalty_matrix_optimized( basis: CustomBasis, ) -> NDArrayFloat: """Optimized version for CustomBasis.""" + if isinstance(basis.fdata, FDataGrid): + product_matrix = np.empty((basis.n_basis, basis.n_basis)) + operator_evaluated = linear_operator(basis.fdata)( + basis.fdata.grid_points[0], + ) + + # Discard last dimension + operator_evaluated = operator_evaluated[..., 0] + for i in range(basis.n_basis): + for j in range(i, basis.n_basis): + product_matrix[i, j] = scipy.integrate.simps( + operator_evaluated[i, :] * operator_evaluated[j, :], + x=basis.fdata.grid_points[0], + ) + product_matrix[j, i] = product_matrix[i, j] + return product_matrix + return gram_matrix(linear_operator, basis.fdata) From fa9e86d1a874d485e8fb513a5cb254abf48a0c7c Mon Sep 17 00:00:00 2001 From: Ddelval Date: Fri, 17 Feb 2023 18:01:34 +0100 Subject: [PATCH 021/192] Improve regularization optimization --- .../_linear_differential_operator.py | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 280fdf6a3..48afd882e 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -1,5 +1,6 @@ from __future__ import annotations +import math import numbers from typing import Callable, Sequence, Union @@ -581,6 +582,7 @@ def bspline_penalty_matrix_optimized( def _optimized_operator_evaluation_in_grid( linear_operator: LinearDifferentialOperator, grid_points: NDArrayFloat, + findiff_accuracy: int, ) -> NDArrayFloat: n_points = grid_points.shape[0] diff_coefficients = np.zeros((n_points, n_points)) @@ -588,16 +590,16 @@ def _optimized_operator_evaluation_in_grid( if w == 0: continue for point_index in range(n_points): - finate_dif = findiff.coefs.coefficients_non_uni( + finite_diff = findiff.coefs.coefficients_non_uni( deriv=dif_order, - acc=2, + acc=findiff_accuracy, coords=grid_points, idx=point_index, ) # The offsets are the relative positions of the # points where each coefficient is applied - offsets = finate_dif["offsets"] - coefficients = finate_dif["coefficients"] + offsets = finite_diff["offsets"] + coefficients = finite_diff["coefficients"] # Add the position of the point where the finite # difference is calculated to get the absolute position @@ -635,28 +637,41 @@ def fdatagrid_penalty_matrix_optimized( Note that the first case is a particular case of the second one. """ + max_deriv = len(linear_operator.weights) - 1 + n_points = basis.grid_points[0].shape[0] + findiff_accuracy = 2 + evaluated_basis = _optimized_operator_evaluation_in_grid( linear_operator, basis.grid_points[0], + findiff_accuracy=findiff_accuracy, ) # The support of the result of applying the linear operator - # can be at most the order of the linear operator plus one. - # Therefore, if the difference between the indeces is greater - # twice the order of the linear operator plus one, the two - # functions are disjoined. - spread = 2 * (len(linear_operator.weights) + 1) - - # Add a border to avoid problems with the integration weights - # at the edges of the interval - border_distance = 5 + # is a small subset of the grid points. We calculate the + # maximum number of points to the left and right of the + # point where the linear operator is applied that can be + # different from zero. + + # Formula derived from the definition of coefficients_not_uni + # in findiff + spread = 2 * math.floor((max_deriv + 1) / 2) - 1 + # The accuracy parameter in findiff increases the number of + # points considered to the left and the right + spread += findiff_accuracy + 1 - n_points = basis.grid_points[0].shape[0] product_matrix = np.zeros((basis.n_samples, basis.n_samples)) for i in range(n_points): - for j in range(i, min(i + spread, n_points)): - left_lim = max(0, i - spread - border_distance) - right_lim = min(n_points, j + spread + border_distance) + look_ahead_limit = min(n_points, i + 2 * spread) + + for j in range(i, look_ahead_limit): + left_lim = i - spread + right_lim = j + spread + # The right limit of the interval is excluded when slicing + right_lim += 1 + + left_lim = max(0, left_lim) + right_lim = min(n_points, right_lim) # Simpson's rule requires an even number of intervals. # If that is not the case, scipy applies a correction @@ -676,10 +691,10 @@ def fdatagrid_penalty_matrix_optimized( evaluated_basis[i, left_lim:right_lim] * evaluated_basis[j, left_lim:right_lim] ) - sub_points = basis.grid_points[0][left_lim:right_lim] + restricted_grid = basis.grid_points[0][left_lim:right_lim] product_matrix[i, j] = scipy.integrate.simpson( operator_product, - sub_points, + restricted_grid, ) product_matrix[j, i] = product_matrix[i, j] From d106fbb438ee9e3a10bd2c9b3c7e3f6eef151462 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Fri, 17 Feb 2023 22:10:46 +0100 Subject: [PATCH 022/192] Complete documentation --- .../_linear_differential_operator.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 48afd882e..85f0c2898 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -584,8 +584,19 @@ def _optimized_operator_evaluation_in_grid( grid_points: NDArrayFloat, findiff_accuracy: int, ) -> NDArrayFloat: + """ + Compute the linear operator applied to the delta basis of the grid. + + The delta basis is the basis where the i-th basis function is 1 in the + i-th point and 0 in the rest of points. The matrix returned by this + function is the matrix where the i-th row contians the values of the + linear operator applied to the i-th basis function of the delta basis. + """ n_points = grid_points.shape[0] diff_coefficients = np.zeros((n_points, n_points)) + + # The desired matrix can be obtained by appending the coefficients + # of the finite differences for each point column-wise. for dif_order, w in enumerate(linear_operator.weights): if w == 0: continue @@ -611,6 +622,8 @@ def _optimized_operator_evaluation_in_grid( else: diff_coefficients[point_index, offsets] += w * coefficients + # Finally, transpose the matrix so that the i-th row contains + # the values of the linear operator applied to the i-th delta function. return diff_coefficients.T @@ -622,20 +635,13 @@ def fdatagrid_penalty_matrix_optimized( """ Optimized version for FDatagrid. - If the data_matrix of the FDataGrid is the identity, the resulting - matrix will be the gram matrix for functions discretized in the - given grid points. That is to say, in order to calculate the norm + The datamatrix of the given FDataGrid has to be the identity matrix, + the resulting matrix will be the gram matrix for functions discretized + in the given grid points. That is to say, in order to calculate the norm of the linear operator applied to functions discretized in the given grid points, it is enough to multiply the returned matrix by the values in the points of the grid on both sides. - If the data_matrix of the FDataGrid is not the identity, the resulting - matrix will be the gram matrix for functions expressed as a combination - of the given set of functions (the entries of the fdatagrid). The - intended use is to calculate the norm of the linear operator for - functions expressed in a basis that is a FDataGrid (see CustomBasis). - - Note that the first case is a particular case of the second one. """ max_deriv = len(linear_operator.weights) - 1 n_points = basis.grid_points[0].shape[0] @@ -654,7 +660,7 @@ def fdatagrid_penalty_matrix_optimized( # different from zero. # Formula derived from the definition of coefficients_not_uni - # in findiff + # in findiff. spread = 2 * math.floor((max_deriv + 1) / 2) - 1 # The accuracy parameter in findiff increases the number of # points considered to the left and the right From 5d6ccda4740c7b706ae4f47a55354c78ebfb7447 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Wed, 1 Mar 2023 22:59:26 +0100 Subject: [PATCH 023/192] Assign the fdata before using it. Otherwise, mypy is not able to infer a type for it. I think that is due to the mutability introduced by having it as a paramter in a function call. --- skfda/representation/basis/_custom_basis.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/skfda/representation/basis/_custom_basis.py b/skfda/representation/basis/_custom_basis.py index 12fd60260..1bec20b7c 100644 --- a/skfda/representation/basis/_custom_basis.py +++ b/skfda/representation/basis/_custom_basis.py @@ -39,9 +39,8 @@ def __init__( domain_range=fdata.domain_range, n_basis=fdata.n_samples, ) - self._check_linearly_independent(fdata) - self.fdata = fdata + self._check_linearly_independent(fdata) @multimethod.multidispatch def _check_linearly_independent(self, fdata: FData) -> None: From bb059682758b1562a298943ed775fcef551804d3 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Tue, 7 Mar 2023 01:23:01 +0100 Subject: [PATCH 024/192] Simplify custombasis_penalty_matrix_optimized --- .../operators/_linear_differential_operator.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 85f0c2898..a21548ff9 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -735,20 +735,14 @@ def custombasis_penalty_matrix_optimized( ) -> NDArrayFloat: """Optimized version for CustomBasis.""" if isinstance(basis.fdata, FDataGrid): - product_matrix = np.empty((basis.n_basis, basis.n_basis)) operator_evaluated = linear_operator(basis.fdata)( basis.fdata.grid_points[0], ) - # Discard last dimension - operator_evaluated = operator_evaluated[..., 0] - for i in range(basis.n_basis): - for j in range(i, basis.n_basis): - product_matrix[i, j] = scipy.integrate.simps( - operator_evaluated[i, :] * operator_evaluated[j, :], - x=basis.fdata.grid_points[0], - ) - product_matrix[j, i] = product_matrix[i, j] - return product_matrix + fdatagrid = FDataGrid( + data_matrix=operator_evaluated[..., 0], + grid_points=basis.fdata.grid_points[0], + ) + return inner_product_matrix(fdatagrid) return gram_matrix(linear_operator, basis.fdata) From 6d60b05417cd85aec81439a78711f555cc975272 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Tue, 7 Mar 2023 01:25:26 +0100 Subject: [PATCH 025/192] Improve fdatagrid penalty matrix calculation --- .../_linear_differential_operator.py | 127 +++++++++++------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index a21548ff9..846ba0106 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -2,14 +2,17 @@ import math import numbers -from typing import Callable, Sequence, Union +from ast import operator +from typing import Callable, Sequence, Tuple, Union import findiff import numpy as np import scipy.integrate from numpy import polyder, polyint, polymul, polyval from scipy.interpolate import PPoly +from sympy import N, product +from ...misc import inner_product_matrix from ...representation import FData, FDataBasis, FDataGrid from ...representation.basis import ( Basis, @@ -20,7 +23,7 @@ MonomialBasis, ) from ...typing._base import DomainRangeLike -from ...typing._numpy import NDArrayFloat +from ...typing._numpy import NDArrayFloat, NDArrayInt from ._operators import Operator, gram_matrix, gram_matrix_optimization Order = int @@ -583,48 +586,79 @@ def _optimized_operator_evaluation_in_grid( linear_operator: LinearDifferentialOperator, grid_points: NDArrayFloat, findiff_accuracy: int, -) -> NDArrayFloat: +) -> Tuple[NDArrayFloat, NDArrayInt]: """ Compute the linear operator applied to the delta basis of the grid. The delta basis is the basis where the i-th basis function is 1 in the - i-th point and 0 in the rest of points. The matrix returned by this - function is the matrix where the i-th row contians the values of the - linear operator applied to the i-th basis function of the delta basis. + i-th point and 0 in the rest of points. + + Returns: + - A matrix where the i-th row contians the values of the linear operator + applied to the i-th basis function of the delta basis. + + - The domain limits of each basis function of the delta basis. Outside + these limits, the result of applying the linear operator to the + given basis function is zero. """ n_points = grid_points.shape[0] diff_coefficients = np.zeros((n_points, n_points)) + domain_limits = np.hstack( + [ + np.ones((n_points, 1)) * n_points, + np.zeros((n_points, 1)), + ], + ).astype(int) + # The desired matrix can be obtained by appending the coefficients # of the finite differences for each point column-wise. for dif_order, w in enumerate(linear_operator.weights): if w == 0: continue for point_index in range(n_points): + # Calculate the finite differences coefficients + # the point point_index. These coefficients express the + # contribution of a function in each of the points in the + # grid to the value of the operator applied to the function + # in the point point_index. finite_diff = findiff.coefs.coefficients_non_uni( deriv=dif_order, acc=findiff_accuracy, coords=grid_points, idx=point_index, ) + # The offsets are the relative positions of the - # points where each coefficient is applied + # points for which the coefficients are being + # calculated offsets = finite_diff["offsets"] coefficients = finite_diff["coefficients"] # Add the position of the point where the finite # difference is calculated to get the absolute position # of the points where each coefficient is applied - offsets += point_index - if callable(w): - coefficients *= w(grid_points[offsets]) - diff_coefficients[point_index, offsets] += coefficients - else: - diff_coefficients[point_index, offsets] += w * coefficients + positions = offsets + point_index + + # If the coefficients for calculating the value of + # the operator in this point (point_index) + # include a coefficient for the i-th point, + # then the i-th basis function contains the point point_index + # in its domain. + # (by definition that fuction is 1 in the i-th point). + domain_limits[positions, 0] = np.minimum( + domain_limits[positions, 0], + np.ones(positions.shape) * point_index, + ) + domain_limits[positions, 1] = np.maximum( + domain_limits[positions, 1], + np.ones(positions.shape) * point_index, + ) + + w_eval = w(grid_points[point_index]) if callable(w) else w + diff_coefficients[positions, point_index] += w_eval * coefficients - # Finally, transpose the matrix so that the i-th row contains - # the values of the linear operator applied to the i-th delta function. - return diff_coefficients.T + return (diff_coefficients, domain_limits) @gram_matrix_optimization.register @@ -643,11 +677,10 @@ def fdatagrid_penalty_matrix_optimized( values in the points of the grid on both sides. """ - max_deriv = len(linear_operator.weights) - 1 n_points = basis.grid_points[0].shape[0] findiff_accuracy = 2 - evaluated_basis = _optimized_operator_evaluation_in_grid( + evaluated_basis, domain_limits = _optimized_operator_evaluation_in_grid( linear_operator, basis.grid_points[0], findiff_accuracy=findiff_accuracy, @@ -659,49 +692,47 @@ def fdatagrid_penalty_matrix_optimized( # point where the linear operator is applied that can be # different from zero. - # Formula derived from the definition of coefficients_not_uni - # in findiff. - spread = 2 * math.floor((max_deriv + 1) / 2) - 1 - # The accuracy parameter in findiff increases the number of - # points considered to the left and the right - spread += findiff_accuracy + 1 + max_domain_width = np.max( + np.abs(domain_limits[:, 0] - domain_limits[:, 1]), + ) + + # Precompute the simpson quadrature for the entire domain. + # This is done to avoid computing it for each subinterval of integration. + quadrature = scipy.integrate.simpson( + np.identity(n_points), + basis.grid_points[0], + ) product_matrix = np.zeros((basis.n_samples, basis.n_samples)) for i in range(n_points): - look_ahead_limit = min(n_points, i + 2 * spread) + # Maximum index of another function with not-disjoint support + max_no_disjoint_index = min(n_points, i + 2 * max_domain_width) + + for j in range(i, max_no_disjoint_index): + if domain_limits[i, 1] < domain_limits[j, 0]: + continue + + left_lim = min( + domain_limits[i, 0], + domain_limits[j, 0], + ) + right_lim = max( + domain_limits[i, 1], + domain_limits[j, 1], + ) - for j in range(i, look_ahead_limit): - left_lim = i - spread - right_lim = j + spread # The right limit of the interval is excluded when slicing right_lim += 1 - left_lim = max(0, left_lim) - right_lim = min(n_points, right_lim) - - # Simpson's rule requires an even number of intervals. - # If that is not the case, scipy applies a correction - # (see simpson docstring). This correction can lead to - # different results when the interval of integration is - # changed. To avoid this, we add a point to the left or - # to the right of the interval. - if (right_lim - left_lim) % 2 == 1: - if right_lim < n_points: - right_lim += 1 - elif left_lim > 0: - left_lim -= 1 - # Limit both the multiplication and the integration to # the interval where the functions are not 0. operator_product = ( evaluated_basis[i, left_lim:right_lim] * evaluated_basis[j, left_lim:right_lim] ) - restricted_grid = basis.grid_points[0][left_lim:right_lim] - product_matrix[i, j] = scipy.integrate.simpson( - operator_product, - restricted_grid, - ) + sub_quadrature = quadrature[left_lim:right_lim] + + product_matrix[i, j] = np.dot(operator_product, sub_quadrature) product_matrix[j, i] = product_matrix[i, j] return product_matrix From d714160c687e8275e5a9c902b1f63649157414af Mon Sep 17 00:00:00 2001 From: Ddelval Date: Tue, 7 Mar 2023 01:40:31 +0100 Subject: [PATCH 026/192] Remove weird imports introduced by autocomplete --- skfda/misc/operators/_linear_differential_operator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 846ba0106..fefc95ad4 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -1,8 +1,6 @@ from __future__ import annotations -import math import numbers -from ast import operator from typing import Callable, Sequence, Tuple, Union import findiff @@ -10,7 +8,6 @@ import scipy.integrate from numpy import polyder, polyint, polymul, polyval from scipy.interpolate import PPoly -from sympy import N, product from ...misc import inner_product_matrix from ...representation import FData, FDataBasis, FDataGrid From 3a950c64712b060048d1ea9b46079a7f46e5200d Mon Sep 17 00:00:00 2001 From: Ddelval Date: Tue, 7 Mar 2023 08:29:39 +0100 Subject: [PATCH 027/192] Avoid circular import --- skfda/misc/operators/_linear_differential_operator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index fefc95ad4..932805487 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -9,7 +9,6 @@ from numpy import polyder, polyint, polymul, polyval from scipy.interpolate import PPoly -from ...misc import inner_product_matrix from ...representation import FData, FDataBasis, FDataGrid from ...representation.basis import ( Basis, @@ -762,6 +761,8 @@ def custombasis_penalty_matrix_optimized( basis: CustomBasis, ) -> NDArrayFloat: """Optimized version for CustomBasis.""" + from ...misc import inner_product_matrix + if isinstance(basis.fdata, FDataGrid): operator_evaluated = linear_operator(basis.fdata)( basis.fdata.grid_points[0], From 5a19294c9faf02ad2f22b6ebf495b233bdda535f Mon Sep 17 00:00:00 2001 From: Ddelval Date: Wed, 15 Mar 2023 15:55:16 +0100 Subject: [PATCH 028/192] Barebones implementation of GridBasis --- .../_linear_differential_operator.py | 14 ++++- skfda/representation/basis/__init__.py | 2 + skfda/representation/basis/_grid_basis.py | 55 +++++++++++++++++++ skfda/tests/test_fpca_regression.py | 2 + 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 skfda/representation/basis/_grid_basis.py diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 932805487..8eef87f97 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -17,6 +17,7 @@ CustomBasis, FourierBasis, MonomialBasis, + GridBasis, ) from ...typing._base import DomainRangeLike from ...typing._numpy import NDArrayFloat, NDArrayInt @@ -661,6 +662,17 @@ def _optimized_operator_evaluation_in_grid( def fdatagrid_penalty_matrix_optimized( linear_operator: LinearDifferentialOperator, basis: FDataGrid, +) -> NDArrayFloat: + return gridbasis_penalty_matrix_optimized( + linear_operator, + GridBasis(grid_points=basis.grid_points), + ) + + +@gram_matrix_optimization.register +def gridbasis_penalty_matrix_optimized( + linear_operator: LinearDifferentialOperator, + basis: GridBasis, ) -> NDArrayFloat: """ Optimized version for FDatagrid. @@ -699,7 +711,7 @@ def fdatagrid_penalty_matrix_optimized( basis.grid_points[0], ) - product_matrix = np.zeros((basis.n_samples, basis.n_samples)) + product_matrix = np.zeros((basis.n_basis, basis.n_basis)) for i in range(n_points): # Maximum index of another function with not-disjoint support max_no_disjoint_index = min(n_points, i + 2 * max_domain_width) diff --git a/skfda/representation/basis/__init__.py b/skfda/representation/basis/__init__.py index b4d9cffd8..9dc082b8f 100644 --- a/skfda/representation/basis/__init__.py +++ b/skfda/representation/basis/__init__.py @@ -13,6 +13,7 @@ "_fdatabasis": ["FDataBasis", "FDataBasisDType"], "_finite_element_basis": ["FiniteElementBasis", "FiniteElement"], "_fourier_basis": ["FourierBasis", "Fourier"], + "_grid_basis": ["GridBasis"], "_monomial_basis": ["MonomialBasis", "Monomial"], "_tensor_basis": ["TensorBasis", "Tensor"], "_vector_basis": ["VectorValuedBasis", "VectorValued"], @@ -42,6 +43,7 @@ Fourier as Fourier, FourierBasis as FourierBasis, ) + from ._grid_basis import GridBasis as GridBasis from ._monomial_basis import ( Monomial as Monomial, MonomialBasis as MonomialBasis, diff --git a/skfda/representation/basis/_grid_basis.py b/skfda/representation/basis/_grid_basis.py new file mode 100644 index 000000000..80c08b3b9 --- /dev/null +++ b/skfda/representation/basis/_grid_basis.py @@ -0,0 +1,55 @@ + +from __future__ import annotations + +from typing import TypeVar + +from ...typing._numpy import NDArrayFloat +from ..evaluator import Evaluator +from ..interpolation import SplineInterpolation +from ._basis import Basis +from ...typing._base import GridPointsLike +from ..._utils import _to_grid_points + +T = TypeVar("T", bound="GridBasis") + + +class GridBasis(Basis): + """ + Basis representing a grid of points. + + Defines a basis whose elements are one in each point of the grid and zero + in the rest. + """ + + def __init__( + self, + *, + grid_points: GridPointsLike, + interpolation: Evaluator | None = None, + ) -> None: + """Basis constructor.""" + self.grid_points = _to_grid_points(grid_points) + self.interpolation = interpolation + domain_range = tuple( + (s[0], s[-1]) for s in self.grid_points + ) + super().__init__( + domain_range=domain_range, + n_basis=len(grid_points[0]), + ) + + @property + def interpolation(self) -> Evaluator: + """Define the type of interpolation applied in `evaluate`.""" + return self._interpolation + + @interpolation.setter + def interpolation(self, new_interpolation: Evaluator | None) -> None: + + if new_interpolation is None: + new_interpolation = SplineInterpolation() + + self._interpolation = new_interpolation + + def _evaluate(self, eval_points: NDArrayFloat) -> NDArrayFloat: + return self.interpolation(eval_points) diff --git a/skfda/tests/test_fpca_regression.py b/skfda/tests/test_fpca_regression.py index 501f424f5..3f58e1615 100644 --- a/skfda/tests/test_fpca_regression.py +++ b/skfda/tests/test_fpca_regression.py @@ -209,4 +209,6 @@ def test_fpca_reg_basis_vs_grid(self): if __name__ == "__main__": + #test = FPCARegressionTestCase() + #test.test_fpca_reg_basis_vs_grid() unittest.main() From 350bce4536be0144694b597e422f230297d11a94 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Wed, 15 Mar 2023 16:09:34 +0100 Subject: [PATCH 029/192] Use gridBasis in fpca regularization --- .../_linear_differential_operator.py | 21 ++----------------- skfda/preprocessing/dim_reduction/_fpca.py | 8 ++----- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 8eef87f97..5f33ca284 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -658,32 +658,15 @@ def _optimized_operator_evaluation_in_grid( return (diff_coefficients, domain_limits) -@gram_matrix_optimization.register -def fdatagrid_penalty_matrix_optimized( - linear_operator: LinearDifferentialOperator, - basis: FDataGrid, -) -> NDArrayFloat: - return gridbasis_penalty_matrix_optimized( - linear_operator, - GridBasis(grid_points=basis.grid_points), - ) - - @gram_matrix_optimization.register def gridbasis_penalty_matrix_optimized( linear_operator: LinearDifferentialOperator, basis: GridBasis, ) -> NDArrayFloat: """ - Optimized version for FDatagrid. - - The datamatrix of the given FDataGrid has to be the identity matrix, - the resulting matrix will be the gram matrix for functions discretized - in the given grid points. That is to say, in order to calculate the norm - of the linear operator applied to functions discretized in the given - grid points, it is enough to multiply the returned matrix by the - values in the points of the grid on both sides. + Optimized version for GridBasis. + It evaluates the operator in the grid points. """ n_points = basis.grid_points[0].shape[0] findiff_accuracy = 2 diff --git a/skfda/preprocessing/dim_reduction/_fpca.py b/skfda/preprocessing/dim_reduction/_fpca.py index fba44acd3..95eb509bf 100644 --- a/skfda/preprocessing/dim_reduction/_fpca.py +++ b/skfda/preprocessing/dim_reduction/_fpca.py @@ -12,6 +12,7 @@ from ..._utils._sklearn_adapter import BaseEstimator, InductiveTransformerMixin from ...misc.regularization import L2Regularization, compute_penalty_matrix from ...representation import FData +from ...representation.basis import GridBasis from ...representation.basis import Basis, FDataBasis from ...representation.grid import FDataGrid from ...typing._numpy import ArrayLike, NDArrayFloat @@ -360,13 +361,8 @@ def _fit_grid( weights_matrix = np.diag(self._weights) - basis = FDataGrid( - data_matrix=np.identity(n_points_discretization), - grid_points=X.grid_points, - ) - regularization_matrix = compute_penalty_matrix( - basis_iterable=(basis,), + basis_iterable=(GridBasis(grid_points=X.grid_points),), regularization_parameter=1, regularization=self.regularization, ) From 138d6782e23964f967b0b6ad0b66209576eab652 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Wed, 15 Mar 2023 16:20:20 +0100 Subject: [PATCH 030/192] Complete grid_basis implementation --- skfda/representation/basis/_grid_basis.py | 56 +++++++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/skfda/representation/basis/_grid_basis.py b/skfda/representation/basis/_grid_basis.py index 80c08b3b9..fd0390f49 100644 --- a/skfda/representation/basis/_grid_basis.py +++ b/skfda/representation/basis/_grid_basis.py @@ -1,24 +1,36 @@ - from __future__ import annotations -from typing import TypeVar +from typing import Any, TypeVar +from ..._utils import _to_grid_points +from ...typing._base import GridPointsLike from ...typing._numpy import NDArrayFloat from ..evaluator import Evaluator from ..interpolation import SplineInterpolation from ._basis import Basis -from ...typing._base import GridPointsLike -from ..._utils import _to_grid_points T = TypeVar("T", bound="GridBasis") class GridBasis(Basis): """ - Basis representing a grid of points. + Basis associated to a grid of points. + + Used to express functions whose values are known in a grid of points. + Each basis function is guaranteed to be zero in all points of the grid + except one, where it is one. The intermediate values are interpolated + depending on the interpolation object passed as argument. + + This basis is used internally as an alternate representation of a + FDataGrid object. In certain cases, it is more convenient to work with + FDataGrid objects as a basis representation in this basis since, then, + all functional data can be represented as an FDataBasis object. + + Parameters: + grid_points: Grid of points where the functions are evaluated. + interpolation: Interpolation object used to interpolate the values + between grid points. By default, a spline is used - Defines a basis whose elements are one in each point of the grid and zero - in the rest. """ def __init__( @@ -30,9 +42,7 @@ def __init__( """Basis constructor.""" self.grid_points = _to_grid_points(grid_points) self.interpolation = interpolation - domain_range = tuple( - (s[0], s[-1]) for s in self.grid_points - ) + domain_range = tuple((s[0], s[-1]) for s in self.grid_points) super().__init__( domain_range=domain_range, n_basis=len(grid_points[0]), @@ -53,3 +63,29 @@ def interpolation(self, new_interpolation: Evaluator | None) -> None: def _evaluate(self, eval_points: NDArrayFloat) -> NDArrayFloat: return self.interpolation(eval_points) + + def __eq__(self, other: Any) -> bool: + return ( + super().__eq__(other) + and self.interpolation == other.interpolation + and self.grid_points == other.grid_points + ) + + def __ne__(self, other: Any) -> bool: + return not self.__eq__(other) + + def __repr__(self) -> str: + return ( + f"{type(self).__name__}(" + f"grid_points={self.grid_points}, " + f"interpolation={self.interpolation})" + ) + + def __hash__(self) -> int: + return ( + super().__hash__() + ^ hash(self.interpolation) + ^ hash( + self.grid_points, + ) + ) From fb127b7469d0ffcc49b27083a8cb52cdc1b82620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Thu, 30 Mar 2023 01:09:16 +0200 Subject: [PATCH 031/192] Cleanup tests --- skfda/tests/test_fdata_cov.py | 66 ++++------------------------------- 1 file changed, 6 insertions(+), 60 deletions(-) diff --git a/skfda/tests/test_fdata_cov.py b/skfda/tests/test_fdata_cov.py index db1af3ca9..a27c8ef8d 100644 --- a/skfda/tests/test_fdata_cov.py +++ b/skfda/tests/test_fdata_cov.py @@ -13,6 +13,9 @@ # FIXTURES ############ +N_SAMPLES = 100 +N_FEATURES = 100 + @pytest.fixture( params=[ @@ -44,30 +47,6 @@ def n_basis( return request.param -@pytest.fixture( - params=[ - 100, - ], -) -def n_samples( - request: Any, -) -> Any: - """Generate number of sample.""" - return request.param - - -@pytest.fixture( - params=[ - 100, - ], -) -def n_features( - request: Any, -) -> Any: - """Generate number of features (points of evaluation).""" - return request.param - - @pytest.fixture( params=[ (0.0, 1.0), @@ -81,30 +60,6 @@ def interval( return request.param -@pytest.fixture( - params=[ - 0.0, - ], -) -def mean( - request: Any, -) -> Any: - """Generate a mean.""" - return request.param - - -@pytest.fixture( - params=[ - 0.1, - ], -) -def noise( - request: Any, -) -> Any: - """Generate a noise value.""" - return request.param - - @pytest.fixture( params=[ Gaussian( @@ -132,11 +87,7 @@ def covariance( def data_in_basis( basis_type: Callable[..., Basis], n_basis: int, - n_samples: int, - n_features: int, interval: Tuple[float, float], - mean: float, - noise: float, covariance: CovarianceLike, ) -> FDataBasis: """Generate gaussian process data using a basis.""" @@ -145,13 +96,11 @@ def data_in_basis( domain_range=interval, ) return make_gaussian_process( - n_samples=n_samples, - n_features=n_features, + n_samples=N_SAMPLES, + n_features=N_FEATURES, start=interval[0], stop=interval[1], - mean=mean, cov=covariance, - noise=noise, ).to_basis(basis) @@ -161,7 +110,6 @@ def data_in_basis( def test_fdatabasis_covariance( data_in_basis: FDataBasis, - n_features: int, ) -> None: """Test the covariance method of FDataBasis. @@ -174,11 +122,9 @@ def test_fdatabasis_covariance( """ # Select grid points before converting to grid domain_range = data_in_basis.domain_range[0] - grid_points = np.linspace(domain_range[0], domain_range[1], n_features) + grid_points = np.linspace(domain_range[0], domain_range[1], N_FEATURES) data_in_grid = data_in_basis.to_grid(grid_points) - s_grid, t_grid = grid_points, grid_points - # Check that the covariance functions are equal np.testing.assert_allclose( data_in_basis.cov(s_grid, t_grid), From 9a96b486e9321e4c1bc7293a802a2326257f36ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Thu, 13 Apr 2023 19:38:02 +0200 Subject: [PATCH 032/192] Use Callable as return for cov method --- skfda/exploratory/stats/_stats.py | 6 +- skfda/exploratory/stats/covariance/_base.py | 5 +- .../stats/covariance/_empirical.py | 9 +- .../stats/covariance/_parametric_gaussian.py | 17 +++- skfda/misc/covariances.py | 90 +++++++++++++++++++ skfda/ml/classification/_qda.py | 5 +- skfda/representation/_functional_data.py | 14 +-- skfda/representation/basis/_fdatabasis.py | 49 ++++------ skfda/representation/grid.py | 50 +++-------- skfda/tests/test_fdata_cov.py | 29 +++++- 10 files changed, 182 insertions(+), 92 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 0676717f6..77df80a6a 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -2,7 +2,7 @@ from __future__ import annotations from builtins import isinstance -from typing import TypeVar, Union +from typing import Callable, TypeVar, Union import numpy as np from scipy import integrate @@ -71,7 +71,7 @@ def gmean(X: FDataGrid) -> FDataGrid: return X.gmean() -def cov(X: F) -> F: +def cov(X: F) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: """ Compute the covariance. @@ -83,7 +83,7 @@ def cov(X: F) -> F: Returns: Covariance of all the samples in the original object, as a - :term:`functional data object` with just one sample. + callable. """ return X.cov() diff --git a/skfda/exploratory/stats/covariance/_base.py b/skfda/exploratory/stats/covariance/_base.py index cf79343e0..a2da0457a 100644 --- a/skfda/exploratory/stats/covariance/_base.py +++ b/skfda/exploratory/stats/covariance/_base.py @@ -1,10 +1,11 @@ from __future__ import annotations from abc import abstractmethod -from typing import Generic, TypeVar +from typing import Callable, Generic, TypeVar from ...._utils._sklearn_adapter import BaseEstimator from ....representation import FData +from ....typing._numpy import NDArrayFloat Input = TypeVar("Input", bound=FData) @@ -15,7 +16,7 @@ class CovarianceEstimator( ): location_: Input - covariance_: Input + covariance_: Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat] def __init__( self, diff --git a/skfda/exploratory/stats/covariance/_empirical.py b/skfda/exploratory/stats/covariance/_empirical.py index ba1efcf03..f7435f951 100644 --- a/skfda/exploratory/stats/covariance/_empirical.py +++ b/skfda/exploratory/stats/covariance/_empirical.py @@ -1,8 +1,9 @@ from __future__ import annotations -from typing import Generic, TypeVar +from typing import Callable, TypeVar from ....representation import FData +from ....typing._numpy import NDArrayFloat from ._base import CovarianceEstimator Input = TypeVar("Input", bound=FData) @@ -11,11 +12,9 @@ class EmpiricalCovariance( CovarianceEstimator[Input], ): + covariance_: Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat] def fit(self, X: Input, y: object = None) -> EmpiricalCovariance[Input]: - - super(EmpiricalCovariance, self).fit(X, y) - + super().fit(X, y) self.covariance_ = X.cov() - return self diff --git a/skfda/exploratory/stats/covariance/_parametric_gaussian.py b/skfda/exploratory/stats/covariance/_parametric_gaussian.py index df22180f4..b11b03e60 100644 --- a/skfda/exploratory/stats/covariance/_parametric_gaussian.py +++ b/skfda/exploratory/stats/covariance/_parametric_gaussian.py @@ -1,16 +1,21 @@ from __future__ import annotations +from typing import Callable + import numpy as np from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import Kernel, WhiteKernel -from ....misc.covariances import Covariance +from ....misc.covariances import Covariance, EmpiricalGrid from ....representation import FDataGrid +from ....typing._numpy import NDArrayFloat from ._empirical import EmpiricalCovariance class ParametricGaussianCovariance(EmpiricalCovariance[FDataGrid]): + covariance_: Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat] + def __init__( self, cov: Kernel | Covariance, @@ -48,8 +53,14 @@ def fit( regressor.fit(grid_points, data_matrix.T) # TODO: Skip cov computation? - self.covariance_ = X.cov().copy( - data_matrix=regressor.kernel_(grid_points)[np.newaxis, ...], + # TODO: Use a user-public structure to represent the covariance, + # instead of a Callable object + self.covariance_ = X.cov() + assert isinstance(self.covariance_, EmpiricalGrid) + self.covariance_.cov_fdata = self.covariance_.cov_fdata.copy( + data_matrix=regressor.kernel_( + grid_points, + )[np.newaxis, ...], ) return self diff --git a/skfda/misc/covariances.py b/skfda/misc/covariances.py index 127643d73..ddfa3fc44 100644 --- a/skfda/misc/covariances.py +++ b/skfda/misc/covariances.py @@ -9,6 +9,8 @@ from matplotlib.figure import Figure from scipy.special import gamma, kv +from ..representation import FData, FDataBasis, FDataGrid +from ..representation.basis import TensorBasis from ..typing._numpy import ArrayLike, NDArrayFloat @@ -758,3 +760,91 @@ def to_sklearn(self) -> sklearn_kern.Kernel: self.variance * sklearn_kern.Matern(length_scale=self.length_scale, nu=self.nu) ) + + +class Empirical(Covariance): + """ + Sample covariance function. + + The sample covariance function is defined as + . math:: + K(t, s) = \frac{1}{n}\sum_{n=1}^N(x_n(t) - \bar{x}(t)) + (x_n(s) - \bar{x}(s)) + + where :math:`x_n(t)` is the n-th sample and :math:`\bar{x}(t)` is the + mean of the samples. + + """ + + _latex_formula = ( + r"K(t, s) = \frac{1}{n}\sum_{n=1}^N(x_n(t) - \bar{x}(t))" + r"(x_n(s) - \bar{x}(s))" + ) + _parameters_str = [ + ("data", "data"), + ] + + cov_fdata: FData + + @abc.abstractmethod + def __init__(self, data: FData) -> None: + if data.dim_domain != 1 or data.dim_codomain != 1: + raise NotImplementedError( + "Covariance only implemented " + "for univariate functions", + ) + + def __call__(self, x: ArrayLike, y: ArrayLike) -> NDArrayFloat: + return self.cov_fdata([x, y], grid=True)[0, ..., 0] + + +class EmpiricalGrid(Empirical): + """ + Sample covariance function for FDataGrid. + """ + + cov_fdata: FDataGrid + + def __init__(self, data: FDataGrid) -> None: + super().__init__(data=data) + + self.cov_fdata = data.copy( + data_matrix=np.cov( + data.data_matrix[..., 0], + rowvar=False, + )[np.newaxis, ...], + grid_points=[ + data.grid_points[0], + data.grid_points[0], + ], + domain_range=[ + data.domain_range[0], + data.domain_range[0], + ], + argument_names=data.argument_names * 2, + sample_names=("covariance",), + ) + + +class EmpiricalBasis(Empirical): + """ + Sample covariance function for FDataBasis. + + In this case one may use the basis expression of the samples to + express the sample covariance function in the tensor product basis + of the original basis. + """ + + cov_fdata: FDataBasis + coeff_matrix: NDArrayFloat + + def __init__(self, data: FDataBasis) -> None: + super().__init__(data=data) + + self.coeff_matrix = np.cov(data.coefficients, rowvar=False) + self.cov_fdata = FDataBasis( + basis=TensorBasis([data.basis, data.basis]), + coefficients=self.coeff_matrix.flatten(), + argument_names=data.argument_names * 2, + sample_names=("covariance",), + ) diff --git a/skfda/ml/classification/_qda.py b/skfda/ml/classification/_qda.py index 628ebdbc1..f1fca3f4c 100644 --- a/skfda/ml/classification/_qda.py +++ b/skfda/ml/classification/_qda.py @@ -207,7 +207,10 @@ def _fit_gaussian_process( cov_estimators.append(cov_estimator) means.append(cov_estimator.location_) - covariance.append(cov_estimator.covariance_.data_matrix[0, ..., 0]) + # TODO: QDA should use the covariance estimators interface + covariance.append( + cov_estimator.covariance_.cov_fdata.data_matrix[0, ..., 0], + ) self.means_ = means self._covariances = np.asarray(covariance) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index fcf1d53b6..f9e4bcb1d 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -11,6 +11,7 @@ from typing import ( TYPE_CHECKING, Any, + Callable, Iterable, Iterator, NoReturn, @@ -830,23 +831,26 @@ def cov( @overload def cov( self: T, - ) -> T: + ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @abstractmethod - def cov( + def cov( # noqa: WPS320 self: T, s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, - ) -> Union[NDArrayFloat, T]: + ) -> Union[ + Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], + NDArrayFloat, + ]: """Compute the covariance of the functional data object. Calculates the unbiased sample covariance function of the data. This is expected to be only defined for univariate functions. The resulting covariance function is defined in the cartesian product of the domain of the functions. - If s_points and t_points are not provided, this method returns - the FData object representing the covariance function. + If s_points or t_points are not provided, this method returns + a callable object representing the covariance function. If s_points and t_points are provided, this method returns the evaluation of the covariance function at the grid formed by the cartesian product of the points in s_points and t_points. diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 0500afe52..e8884ad97 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -6,6 +6,7 @@ from typing import ( TYPE_CHECKING, Any, + Callable, Optional, Sequence, Type, @@ -473,26 +474,27 @@ def cov( @overload def cov( self: T, - ) -> T: + ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass - def cov( + def cov( # noqa: WPS320 self: T, s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, - ) -> Union[T, NDArrayFloat]: + ) -> Union[ + Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], + NDArrayFloat, + ]: """Compute the covariance of the functional data object. Calculates the unbiased sample covariance function of the data. This is expected to be only defined for univariate functions. This is a function defined over the basis consisting of the tensor product of the original basis with itself. The resulting covariance - function is then represented as an FDataBasis object with one - sample with such tensor basis with the coefficients being the - flattened covariance matrix of the original coefficients. + function is then represented as a callable object. - If s_points and t_points are not provided, this method returns - the FData object representing the covariance function. + If s_points or t_points are not provided, this method returns + a callable object representing the covariance function. If s_points and t_points are provided, this method returns the evaluation of the covariance function at the grid formed by the cartesian product of the points in s_points and t_points. @@ -505,31 +507,12 @@ def cov( Covariance function. """ - # import TensorBasis here to avoid circular dependencies - from . import TensorBasis - - dataset_name = ( - f"{self.dataset_name} - covariance" - if self.dataset_name is not None else None - ) - - basis = TensorBasis([self.basis, self.basis]) - coefficients = np.cov(self.coefficients, rowvar=False).flatten() - - covariance_function = FDataBasis( - basis=basis, - coefficients=coefficients, - dataset_name=dataset_name, - argument_names=self.argument_names * 2, - sample_names=("covariance",), - ) - - if s_points is not None and t_points is not None: - return covariance_function( - [s_points, t_points], - grid=True, - )[0, ..., 0] - return covariance_function + # To avoid circular imports + from ...misc.covariances import EmpiricalBasis + cov_function = EmpiricalBasis(self) + if s_points is None or t_points is None: + return cov_function + return cov_function(s_points, t_points) def to_grid( self, diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 25f33cba8..554ec7459 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -13,6 +13,7 @@ from typing import ( TYPE_CHECKING, Any, + Callable, Optional, Sequence, Type, @@ -605,14 +606,17 @@ def cov( @overload def cov( self: T, - ) -> T: + ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass - def cov( + def cov( # noqa: WPS320 self: T, s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, - ) -> Union[T, NDArrayFloat]: + ) -> Union[ + Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], + NDArrayFloat, + ]: """Compute the covariance. Calculates the covariance matrix representing the covariance of the @@ -629,40 +633,12 @@ def cov( Covariance function. """ - dataset_name = ( - f"{self.dataset_name} - covariance" - if self.dataset_name is not None else None - ) - - if self.dim_domain != 1 or self.dim_codomain != 1: - raise NotImplementedError( - "Covariance only implemented " - "for univariate functions", - ) - - covariance_function = self.copy( - data_matrix=np.cov( - self.data_matrix[..., 0], - rowvar=False, - )[np.newaxis, ...], - grid_points=[ - self.grid_points[0], - self.grid_points[0], - ], - domain_range=[ - self.domain_range[0], - self.domain_range[0], - ], - dataset_name=dataset_name, - argument_names=self.argument_names * 2, - sample_names=("covariance",), - ) - if s_points is not None and t_points is not None: - return covariance_function( - [s_points, t_points], - grid=True, - )[0, ..., 0] - return covariance_function + # To avoid circular imports + from ..misc.covariances import EmpiricalGrid + cov_function = EmpiricalGrid(self) + if s_points is None or t_points is None: + return cov_function + return cov_function(s_points, t_points) def gmean(self: T) -> T: """Compute the geometric mean of all samples in the FDataGrid object. diff --git a/skfda/tests/test_fdata_cov.py b/skfda/tests/test_fdata_cov.py index a27c8ef8d..b4d33b2fa 100644 --- a/skfda/tests/test_fdata_cov.py +++ b/skfda/tests/test_fdata_cov.py @@ -108,6 +108,30 @@ def data_in_basis( # TESTS ############ +def test_overload_cov() -> None: + """Test that the overloading of the cov method is consistent.""" + # Generate any sample data + data = make_gaussian_process( + n_samples=N_SAMPLES, + n_features=N_FEATURES, + start=0, + stop=1, + ) + grid_points = np.linspace(0, 1, N_FEATURES) + # Test for FDataGrid + np.testing.assert_equal( + data.cov()(grid_points, grid_points), + data.cov(grid_points, grid_points), + ) + # Test for FDataBasis + basis = FourierBasis(n_basis=5, domain_range=(0, 1)) + data_in_basis = data.to_basis(basis) + np.testing.assert_equal( + data_in_basis.cov()(grid_points, grid_points), + data_in_basis.cov(grid_points, grid_points), + ) + + def test_fdatabasis_covariance( data_in_basis: FDataBasis, ) -> None: @@ -124,9 +148,8 @@ def test_fdatabasis_covariance( domain_range = data_in_basis.domain_range[0] grid_points = np.linspace(domain_range[0], domain_range[1], N_FEATURES) data_in_grid = data_in_basis.to_grid(grid_points) - s_grid, t_grid = grid_points, grid_points # Check that the covariance functions are equal np.testing.assert_allclose( - data_in_basis.cov(s_grid, t_grid), - data_in_grid.cov(s_grid, t_grid), + data_in_basis.cov(grid_points, grid_points), + data_in_grid.cov(grid_points, grid_points), ) From 21c5fe80b782822fd09a5985024cfc3d21451be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Thu, 13 Apr 2023 19:47:03 +0200 Subject: [PATCH 033/192] Fix docstrings. --- skfda/misc/covariances.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/skfda/misc/covariances.py b/skfda/misc/covariances.py index ddfa3fc44..2d4d444f0 100644 --- a/skfda/misc/covariances.py +++ b/skfda/misc/covariances.py @@ -763,13 +763,13 @@ def to_sklearn(self) -> sklearn_kern.Kernel: class Empirical(Covariance): - """ + r""" Sample covariance function. The sample covariance function is defined as . math:: - K(t, s) = \frac{1}{n}\sum_{n=1}^N(x_n(t) - \bar{x}(t)) - (x_n(s) - \bar{x}(s)) + K(t, s) = \frac{1}{n}\sum_{n=1}^N\left(x_n(t) - \bar{x}(t)\right) + \left(x_n(s) - \bar{x}(s)\right) where :math:`x_n(t)` is the n-th sample and :math:`\bar{x}(t)` is the mean of the samples. @@ -795,13 +795,20 @@ def __init__(self, data: FData) -> None: ) def __call__(self, x: ArrayLike, y: ArrayLike) -> NDArrayFloat: + """Evaluate the covariance function. + + Args: + x: First array of points of evaluation. + y: Second array of points of evaluation. + + Returns: + Covariance function evaluated at the grid formed by x and y. + """ return self.cov_fdata([x, y], grid=True)[0, ..., 0] class EmpiricalGrid(Empirical): - """ - Sample covariance function for FDataGrid. - """ + """Sample covariance function for FDataGrid.""" cov_fdata: FDataGrid From 6b08ed7477ee1712164d77a926acfa9ecc1590a5 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 01:53:33 +0200 Subject: [PATCH 034/192] Simplify GridBasis implementation With these changes, a FDataBasis using a GridBasis cannot be evaluated. This functionality is removed since, when evaluation is required, an FDataGrid should be used --- .../_linear_differential_operator.py | 4 +-- skfda/preprocessing/dim_reduction/_fpca.py | 5 ++- skfda/representation/basis/__init__.py | 4 +-- skfda/representation/basis/_grid_basis.py | 31 +++---------------- 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 5f33ca284..e2f989390 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -17,7 +17,7 @@ CustomBasis, FourierBasis, MonomialBasis, - GridBasis, + _GridBasis, ) from ...typing._base import DomainRangeLike from ...typing._numpy import NDArrayFloat, NDArrayInt @@ -661,7 +661,7 @@ def _optimized_operator_evaluation_in_grid( @gram_matrix_optimization.register def gridbasis_penalty_matrix_optimized( linear_operator: LinearDifferentialOperator, - basis: GridBasis, + basis: _GridBasis, ) -> NDArrayFloat: """ Optimized version for GridBasis. diff --git a/skfda/preprocessing/dim_reduction/_fpca.py b/skfda/preprocessing/dim_reduction/_fpca.py index 95eb509bf..f5c88a54a 100644 --- a/skfda/preprocessing/dim_reduction/_fpca.py +++ b/skfda/preprocessing/dim_reduction/_fpca.py @@ -12,8 +12,7 @@ from ..._utils._sklearn_adapter import BaseEstimator, InductiveTransformerMixin from ...misc.regularization import L2Regularization, compute_penalty_matrix from ...representation import FData -from ...representation.basis import GridBasis -from ...representation.basis import Basis, FDataBasis +from ...representation.basis import Basis, FDataBasis, _GridBasis from ...representation.grid import FDataGrid from ...typing._numpy import ArrayLike, NDArrayFloat @@ -362,7 +361,7 @@ def _fit_grid( weights_matrix = np.diag(self._weights) regularization_matrix = compute_penalty_matrix( - basis_iterable=(GridBasis(grid_points=X.grid_points),), + basis_iterable=(_GridBasis(grid_points=X.grid_points),), regularization_parameter=1, regularization=self.regularization, ) diff --git a/skfda/representation/basis/__init__.py b/skfda/representation/basis/__init__.py index 9dc082b8f..553cfdb80 100644 --- a/skfda/representation/basis/__init__.py +++ b/skfda/representation/basis/__init__.py @@ -13,7 +13,7 @@ "_fdatabasis": ["FDataBasis", "FDataBasisDType"], "_finite_element_basis": ["FiniteElementBasis", "FiniteElement"], "_fourier_basis": ["FourierBasis", "Fourier"], - "_grid_basis": ["GridBasis"], + "_grid_basis": ["_GridBasis"], "_monomial_basis": ["MonomialBasis", "Monomial"], "_tensor_basis": ["TensorBasis", "Tensor"], "_vector_basis": ["VectorValuedBasis", "VectorValued"], @@ -43,7 +43,7 @@ Fourier as Fourier, FourierBasis as FourierBasis, ) - from ._grid_basis import GridBasis as GridBasis + from ._grid_basis import _GridBasis as _GridBasis from ._monomial_basis import ( Monomial as Monomial, MonomialBasis as MonomialBasis, diff --git a/skfda/representation/basis/_grid_basis.py b/skfda/representation/basis/_grid_basis.py index fd0390f49..ab20a55d4 100644 --- a/skfda/representation/basis/_grid_basis.py +++ b/skfda/representation/basis/_grid_basis.py @@ -6,13 +6,12 @@ from ...typing._base import GridPointsLike from ...typing._numpy import NDArrayFloat from ..evaluator import Evaluator -from ..interpolation import SplineInterpolation from ._basis import Basis -T = TypeVar("T", bound="GridBasis") +T = TypeVar("T", bound="_GridBasis") -class GridBasis(Basis): +class _GridBasis(Basis): """ Basis associated to a grid of points. @@ -28,8 +27,6 @@ class GridBasis(Basis): Parameters: grid_points: Grid of points where the functions are evaluated. - interpolation: Interpolation object used to interpolate the values - between grid points. By default, a spline is used """ @@ -41,50 +38,32 @@ def __init__( ) -> None: """Basis constructor.""" self.grid_points = _to_grid_points(grid_points) - self.interpolation = interpolation domain_range = tuple((s[0], s[-1]) for s in self.grid_points) super().__init__( domain_range=domain_range, n_basis=len(grid_points[0]), ) - @property - def interpolation(self) -> Evaluator: - """Define the type of interpolation applied in `evaluate`.""" - return self._interpolation - - @interpolation.setter - def interpolation(self, new_interpolation: Evaluator | None) -> None: - - if new_interpolation is None: - new_interpolation = SplineInterpolation() - - self._interpolation = new_interpolation - def _evaluate(self, eval_points: NDArrayFloat) -> NDArrayFloat: - return self.interpolation(eval_points) + raise NotImplementedError( + "Evaluation is not implemented in this basis", + ) def __eq__(self, other: Any) -> bool: return ( super().__eq__(other) - and self.interpolation == other.interpolation and self.grid_points == other.grid_points ) - def __ne__(self, other: Any) -> bool: - return not self.__eq__(other) - def __repr__(self) -> str: return ( f"{type(self).__name__}(" f"grid_points={self.grid_points}, " - f"interpolation={self.interpolation})" ) def __hash__(self) -> int: return ( super().__hash__() - ^ hash(self.interpolation) ^ hash( self.grid_points, ) From 8f39f184135047e7630ccaf33a9b330aea725e50 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 01:59:28 +0200 Subject: [PATCH 035/192] Extract fdatagrid specialization to function --- .../_linear_differential_operator.py | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index e2f989390..9002e8f40 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -749,6 +749,22 @@ def fdatabasis_penalty_matrix_optimized( coef_matrix = fdatabasis.coefficients return coef_matrix @ basis_pen_matrix @ coef_matrix.T +@gram_matrix_optimization.register +def fdatagrid_penalty_matrix_optimized( + linear_operator: LinearDifferentialOperator, + fdatagrid: FDataGrid, +) -> NDArrayFloat: + from ...misc import inner_product_matrix + """Optimized version for FDataGrid.""" + operator_evaluated = linear_operator(fdatagrid)( + fdatagrid.grid_points[0], + ) + + fdatagrid_evaluated = FDataGrid( + data_matrix=operator_evaluated[..., 0], + grid_points=fdatagrid.grid_points[0], + ) + return inner_product_matrix(fdatagrid_evaluated) @gram_matrix_optimization.register def custombasis_penalty_matrix_optimized( @@ -756,17 +772,4 @@ def custombasis_penalty_matrix_optimized( basis: CustomBasis, ) -> NDArrayFloat: """Optimized version for CustomBasis.""" - from ...misc import inner_product_matrix - - if isinstance(basis.fdata, FDataGrid): - operator_evaluated = linear_operator(basis.fdata)( - basis.fdata.grid_points[0], - ) - - fdatagrid = FDataGrid( - data_matrix=operator_evaluated[..., 0], - grid_points=basis.fdata.grid_points[0], - ) - return inner_product_matrix(fdatagrid) - return gram_matrix(linear_operator, basis.fdata) From aba68ec0feb6fb7562d3b956e1a0d145b18d52da Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 02:08:17 +0200 Subject: [PATCH 036/192] Style fixes --- skfda/misc/operators/_linear_differential_operator.py | 7 +++---- skfda/tests/test_fpca_regression.py | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 9002e8f40..78641c945 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -748,18 +748,17 @@ def fdatabasis_penalty_matrix_optimized( coef_matrix = fdatabasis.coefficients return coef_matrix @ basis_pen_matrix @ coef_matrix.T - -@gram_matrix_optimization.register + def fdatagrid_penalty_matrix_optimized( linear_operator: LinearDifferentialOperator, fdatagrid: FDataGrid, ) -> NDArrayFloat: - from ...misc import inner_product_matrix """Optimized version for FDataGrid.""" + from ...misc import inner_product_matrix + operator_evaluated = linear_operator(fdatagrid)( fdatagrid.grid_points[0], ) - fdatagrid_evaluated = FDataGrid( data_matrix=operator_evaluated[..., 0], grid_points=fdatagrid.grid_points[0], diff --git a/skfda/tests/test_fpca_regression.py b/skfda/tests/test_fpca_regression.py index 3f58e1615..501f424f5 100644 --- a/skfda/tests/test_fpca_regression.py +++ b/skfda/tests/test_fpca_regression.py @@ -209,6 +209,4 @@ def test_fpca_reg_basis_vs_grid(self): if __name__ == "__main__": - #test = FPCARegressionTestCase() - #test.test_fpca_reg_basis_vs_grid() unittest.main() From ed68f683208148e69342358d3671620b182ddc46 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 02:10:15 +0200 Subject: [PATCH 037/192] More style changes --- skfda/misc/operators/_linear_differential_operator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 78641c945..aab00bb3e 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -748,7 +748,8 @@ def fdatabasis_penalty_matrix_optimized( coef_matrix = fdatabasis.coefficients return coef_matrix @ basis_pen_matrix @ coef_matrix.T - + + def fdatagrid_penalty_matrix_optimized( linear_operator: LinearDifferentialOperator, fdatagrid: FDataGrid, From 3bfab32c825207ac6f8fd471c5dee1bc9c12fda6 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 02:42:43 +0200 Subject: [PATCH 038/192] Fix unregistered function --- skfda/misc/operators/_linear_differential_operator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index aab00bb3e..9be252487 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -750,6 +750,7 @@ def fdatabasis_penalty_matrix_optimized( return coef_matrix @ basis_pen_matrix @ coef_matrix.T +@gram_matrix_optimization.register def fdatagrid_penalty_matrix_optimized( linear_operator: LinearDifferentialOperator, fdatagrid: FDataGrid, From f8d4554a8a4f42c4b99978562dece222b9db9dde Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 11:27:21 +0200 Subject: [PATCH 039/192] Add _optimized_operator_evaluation_in_grid example --- .../_linear_differential_operator.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 9be252487..4f026f4c6 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -597,6 +597,38 @@ def _optimized_operator_evaluation_in_grid( - The domain limits of each basis function of the delta basis. Outside these limits, the result of applying the linear operator to the given basis function is zero. + + Example: + >>> import numpy as np + >>> from skfda.misc.operators import LinearDifferentialOperator + >>> _optimized_operator_evaluation_in_grid( + ... LinearDifferentialOperator(2), + ... np.linspace(0, 7, 8), + ... 2, + ... ) + (array([[ 2., 1., 0., 0., 0., 0., 0., 0.], + [-5., -2., 1., 0., 0., 0., 0., 0.], + [ 4., 1., -2., 1., 0., 0., 0., 0.], + [-1., 0., 1., -2., 1., 0., 0., 0.], + [ 0., 0., 0., 1., -2., 1., 0., -1.], + [ 0., 0., 0., 0., 1., -2., 1., 4.], + [ 0., 0., 0., 0., 0., 1., -2., -5.], + [ 0., 0., 0., 0., 0., 0., 1., 2.]]), + array([[0, 1], + [0, 2], + [0, 3], + [0, 4], + [3, 7], + [4, 7], + [5, 7], + [6, 7]])) + Explanation: + Each row of the first array contains the values of the linear operator + applied to the delta basis function. As it can be seen, each row of the + second array cointains the domain limits of the corresponding row of the + first array. That is to say, the range in which their values are not 0. + For example, the third row of the first array is non-zero until the fourth + element, so the third row of the second array is [0, 3]. """ n_points = grid_points.shape[0] diff_coefficients = np.zeros((n_points, n_points)) From c1389d9d165032011ea418cfab7156967c0e02bb Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 11:51:30 +0200 Subject: [PATCH 040/192] Add a test for the GridBasis penalty matrix --- .../test_linear_differential_operator.py | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/skfda/tests/test_linear_differential_operator.py b/skfda/tests/test_linear_differential_operator.py index 2a77eafe9..79ebd0578 100644 --- a/skfda/tests/test_linear_differential_operator.py +++ b/skfda/tests/test_linear_differential_operator.py @@ -7,7 +7,9 @@ from skfda.misc.operators import LinearDifferentialOperator from skfda.representation.basis import ConstantBasis, FDataBasis, MonomialBasis - +from skfda.misc.operators import gram_matrix +from skfda.representation.basis import _GridBasis +from skfda.representation.grid import FDataGrid WeightCallable = Callable[[np.ndarray], np.ndarray] @@ -122,6 +124,36 @@ def test_init_wrong_params(self) -> None: domain_range=(0, 2), ) + def test_grid_basis_penalty_matrix(self) -> None: + """Check the penalty matrix for a grid basis.""" + + n_points = 10000 + n_functions = 10 + linear_operator = LinearDifferentialOperator(order=2) + grid_points = np.linspace(0, 100, n_points) + + # Generate random functions + random_gen = np.random.default_rng(0) + data_matrix = random_gen.random(size=(n_functions, n_points)) + fd = FDataGrid(data_matrix=data_matrix, grid_points=grid_points) + + # Calcualate the norm of the second derivatives using the + # penalty matrix + + penalty_matrix = gram_matrix( + linear_operator=linear_operator, + basis=_GridBasis(grid_points=fd.grid_points), + ) + pen_mat_norms = np.diagonal(data_matrix @ penalty_matrix @ data_matrix.T) + + # Calculate the norm of the second derivatives direclty by derivating, + # squaring the functions and integrating + fd_dif = fd.derivative(order=2) + int_norms = (fd_dif * fd_dif).integrate().flatten() + + np.testing.assert_allclose(pen_mat_norms, int_norms) + + if __name__ == '__main__': unittest.main() From 188e7bd14cdd43820cb2ebbf99ee23402eb1fa9b Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 11:52:13 +0200 Subject: [PATCH 041/192] Sort imports --- skfda/tests/test_linear_differential_operator.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/skfda/tests/test_linear_differential_operator.py b/skfda/tests/test_linear_differential_operator.py index 79ebd0578..a15d3cee5 100644 --- a/skfda/tests/test_linear_differential_operator.py +++ b/skfda/tests/test_linear_differential_operator.py @@ -5,11 +5,15 @@ import numpy as np -from skfda.misc.operators import LinearDifferentialOperator -from skfda.representation.basis import ConstantBasis, FDataBasis, MonomialBasis -from skfda.misc.operators import gram_matrix -from skfda.representation.basis import _GridBasis +from skfda.misc.operators import LinearDifferentialOperator, gram_matrix +from skfda.representation.basis import ( + ConstantBasis, + FDataBasis, + MonomialBasis, + _GridBasis, +) from skfda.representation.grid import FDataGrid + WeightCallable = Callable[[np.ndarray], np.ndarray] From 93bd28f53df8c8b290365e6183450c849fa20f74 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 11:54:51 +0200 Subject: [PATCH 042/192] Style fixes --- skfda/misc/operators/_linear_differential_operator.py | 11 ++++++----- skfda/tests/test_linear_differential_operator.py | 9 +++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 4f026f4c6..a57010c82 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -624,11 +624,12 @@ def _optimized_operator_evaluation_in_grid( [6, 7]])) Explanation: Each row of the first array contains the values of the linear operator - applied to the delta basis function. As it can be seen, each row of the - second array cointains the domain limits of the corresponding row of the - first array. That is to say, the range in which their values are not 0. - For example, the third row of the first array is non-zero until the fourth - element, so the third row of the second array is [0, 3]. + applied to the delta basis function. + As it can be seen, each row of the second array cointains the domain + limits of the corresponding row of the first array. That is to say, + the range in which their values are not 0. + For example, the third row of the first array is non-zero until the + fourth element, so the third row of the second array is [0, 3]. """ n_points = grid_points.shape[0] diff_coefficients = np.zeros((n_points, n_points)) diff --git a/skfda/tests/test_linear_differential_operator.py b/skfda/tests/test_linear_differential_operator.py index a15d3cee5..30813f611 100644 --- a/skfda/tests/test_linear_differential_operator.py +++ b/skfda/tests/test_linear_differential_operator.py @@ -130,7 +130,6 @@ def test_init_wrong_params(self) -> None: def test_grid_basis_penalty_matrix(self) -> None: """Check the penalty matrix for a grid basis.""" - n_points = 10000 n_functions = 10 linear_operator = LinearDifferentialOperator(order=2) @@ -148,10 +147,12 @@ def test_grid_basis_penalty_matrix(self) -> None: linear_operator=linear_operator, basis=_GridBasis(grid_points=fd.grid_points), ) - pen_mat_norms = np.diagonal(data_matrix @ penalty_matrix @ data_matrix.T) + pen_mat_norms = np.diagonal( + data_matrix @ penalty_matrix @ data_matrix.T, + ) - # Calculate the norm of the second derivatives direclty by derivating, - # squaring the functions and integrating + # Calculate the norm of the second derivatives direclty + # by derivating, squaring the functions and integrating fd_dif = fd.derivative(order=2) int_norms = (fd_dif * fd_dif).integrate().flatten() From 979e8d266730be3ff1e58985c52637b19c721de9 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 11:57:20 +0200 Subject: [PATCH 043/192] Remove trailing whitespace --- skfda/tests/test_linear_differential_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/tests/test_linear_differential_operator.py b/skfda/tests/test_linear_differential_operator.py index 30813f611..a0d1982fb 100644 --- a/skfda/tests/test_linear_differential_operator.py +++ b/skfda/tests/test_linear_differential_operator.py @@ -151,7 +151,7 @@ def test_grid_basis_penalty_matrix(self) -> None: data_matrix @ penalty_matrix @ data_matrix.T, ) - # Calculate the norm of the second derivatives direclty + # Calculate the norm of the second derivatives direclty # by derivating, squaring the functions and integrating fd_dif = fd.derivative(order=2) int_norms = (fd_dif * fd_dif).integrate().flatten() From 5d0a9e02b6f39cf032bafae42903053bd805607e Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 11:59:35 +0200 Subject: [PATCH 044/192] Fix docstring spacing --- skfda/misc/operators/_linear_differential_operator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index a57010c82..0fda06845 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -622,6 +622,7 @@ def _optimized_operator_evaluation_in_grid( [4, 7], [5, 7], [6, 7]])) + Explanation: Each row of the first array contains the values of the linear operator applied to the delta basis function. From 7bdc97dd69a484833f8d67b63d7fd59918ce64f7 Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 12:00:57 +0200 Subject: [PATCH 045/192] Remove left-over parameter --- skfda/representation/basis/_grid_basis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skfda/representation/basis/_grid_basis.py b/skfda/representation/basis/_grid_basis.py index ab20a55d4..0dffbb870 100644 --- a/skfda/representation/basis/_grid_basis.py +++ b/skfda/representation/basis/_grid_basis.py @@ -34,7 +34,6 @@ def __init__( self, *, grid_points: GridPointsLike, - interpolation: Evaluator | None = None, ) -> None: """Basis constructor.""" self.grid_points = _to_grid_points(grid_points) From 4b46b1cff0a92fa1aa03986cbed674423f94717b Mon Sep 17 00:00:00 2001 From: Ddelval Date: Sun, 16 Apr 2023 12:03:30 +0200 Subject: [PATCH 046/192] Remove left-over import --- skfda/representation/basis/_grid_basis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skfda/representation/basis/_grid_basis.py b/skfda/representation/basis/_grid_basis.py index 0dffbb870..2533cbc02 100644 --- a/skfda/representation/basis/_grid_basis.py +++ b/skfda/representation/basis/_grid_basis.py @@ -5,7 +5,6 @@ from ..._utils import _to_grid_points from ...typing._base import GridPointsLike from ...typing._numpy import NDArrayFloat -from ..evaluator import Evaluator from ._basis import Basis T = TypeVar("T", bound="_GridBasis") From 4fb92310fd0837d7cf1ecf3736fc295465356eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Tue, 13 Jun 2023 12:01:52 +0200 Subject: [PATCH 047/192] Remove parameter: default_n_features --- skfda/inference/anova/_anova_oneway.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/skfda/inference/anova/_anova_oneway.py b/skfda/inference/anova/_anova_oneway.py index 94a78201f..661323ab7 100644 --- a/skfda/inference/anova/_anova_oneway.py +++ b/skfda/inference/anova/_anova_oneway.py @@ -191,7 +191,6 @@ def _anova_bootstrap( random_state: RandomStateLike = None, p: int = 2, equal_var: bool = True, - default_n_features: int = constants.N_POINTS_FINE_MESH, ) -> NDArrayFloat: n_groups = len(fd_grouped) @@ -215,8 +214,7 @@ def _anova_bootstrap( grid_points = getattr(fd_grouped[0], "grid_points", None) if grid_points is None: start, stop = fd_grouped[0].domain_range[0] - n_features = default_n_features - grid_points = np.linspace(start, stop, n_features) + grid_points = np.linspace(start, stop, constants.N_POINTS_FINE_MESH) if equal_var: cov_est = concatenate(fd_grouped).cov( From 3738a9f0c55652403e956d2372434b4f60b72dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Tue, 13 Jun 2023 12:03:10 +0200 Subject: [PATCH 048/192] Use FData instead of generic F --- skfda/exploratory/stats/_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 77df80a6a..e3e9050d2 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -71,7 +71,7 @@ def gmean(X: FDataGrid) -> FDataGrid: return X.gmean() -def cov(X: F) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: +def cov(X: FData) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: """ Compute the covariance. From c60c72ee16745b39537af4271caac2cd5a117646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Tue, 13 Jun 2023 12:07:38 +0200 Subject: [PATCH 049/192] Positional use of s_points and t_points in cov() calls --- skfda/inference/anova/_anova_oneway.py | 8 ++++---- skfda/inference/hotelling/_hotelling.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/skfda/inference/anova/_anova_oneway.py b/skfda/inference/anova/_anova_oneway.py index 661323ab7..9826c1655 100644 --- a/skfda/inference/anova/_anova_oneway.py +++ b/skfda/inference/anova/_anova_oneway.py @@ -218,16 +218,16 @@ def _anova_bootstrap( if equal_var: cov_est = concatenate(fd_grouped).cov( - s_points=grid_points, - t_points=grid_points, + grid_points, + grid_points, ) k_est = [cov_est] * len(fd_grouped) else: # Estimating covariances for each group k_est = [ fdg.cov( - s_points=grid_points, - t_points=grid_points, + grid_points, + grid_points, ) for fdg in fd_grouped ] diff --git a/skfda/inference/hotelling/_hotelling.py b/skfda/inference/hotelling/_hotelling.py index e24664f53..9f07e4944 100644 --- a/skfda/inference/hotelling/_hotelling.py +++ b/skfda/inference/hotelling/_hotelling.py @@ -101,12 +101,12 @@ def hotelling_t2( # Working with standard discretized data m = m.data_matrix[0, ..., 0] k1 = fd1.cov( - s_points=fd1.grid_points[0], - t_points=fd1.grid_points[0], + fd1.grid_points[0], + fd1.grid_points[0], ) k2 = fd2.cov( - s_points=fd2.grid_points[0], - t_points=fd2.grid_points[0], + fd2.grid_points[0], + fd2.grid_points[0], ) m = m.reshape((-1, 1)) # Reshaping the mean for a proper matrix product From d1c26320b997bb11543cd2c81380a9a195e1e85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Tue, 13 Jun 2023 12:32:23 +0200 Subject: [PATCH 050/192] Positional-only parameters for cov(s_points, t_points) --- skfda/representation/_functional_data.py | 1 + skfda/representation/basis/_fdatabasis.py | 1 + skfda/representation/grid.py | 1 + 3 files changed, 3 insertions(+) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index f9e4bcb1d..57feb3226 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -823,6 +823,7 @@ def sum( # noqa: WPS125 @overload def cov( self: T, + /, s_points: NDArrayFloat, t_points: NDArrayFloat, ) -> NDArrayFloat: diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index e8884ad97..db4d8d577 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -466,6 +466,7 @@ def var(self: T, eval_points: Optional[NDArrayFloat] = None) -> T: @overload def cov( self: T, + /, s_points: NDArrayFloat, t_points: NDArrayFloat, ) -> NDArrayFloat: diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 554ec7459..9235c4c05 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -598,6 +598,7 @@ def var(self: T) -> T: @overload def cov( self: T, + /, s_points: NDArrayFloat, t_points: NDArrayFloat, ) -> NDArrayFloat: From 5e1e45c637e0a2cf84f860854ae7a055120310a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Tue, 13 Jun 2023 19:00:43 +0200 Subject: [PATCH 051/192] Allow positional-only parameters via noqa --- skfda/representation/_functional_data.py | 2 +- skfda/representation/basis/_fdatabasis.py | 2 +- skfda/representation/grid.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index 57feb3226..8d35811b0 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -821,7 +821,7 @@ def sum( # noqa: WPS125 return self @overload - def cov( + def cov( # noqa: WPS451 self: T, /, s_points: NDArrayFloat, diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index db4d8d577..670bf6e50 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -464,7 +464,7 @@ def var(self: T, eval_points: Optional[NDArrayFloat] = None) -> T: return self.to_grid(eval_points).var().to_basis(self.basis) @overload - def cov( + def cov( # noqa: WPS451 self: T, /, s_points: NDArrayFloat, diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 9235c4c05..4ff68af99 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -596,7 +596,7 @@ def var(self: T) -> T: ) @overload - def cov( + def cov( # noqa: WPS451 self: T, /, s_points: NDArrayFloat, From 507595aa46c4e8f9b61e3a7e3307e55e61b448a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Fri, 16 Jun 2023 15:40:49 +0200 Subject: [PATCH 052/192] SRSF formula doc fix --- skfda/misc/operators/_srvf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skfda/misc/operators/_srvf.py b/skfda/misc/operators/_srvf.py index b1e406b7a..7764440ea 100644 --- a/skfda/misc/operators/_srvf.py +++ b/skfda/misc/operators/_srvf.py @@ -24,7 +24,7 @@ class SRSF( function, the SRSF transform is defined as .. math:: - SRSF(f(t)) = sgn(f(t)) \sqrt{|\dot f(t)|} = q(t) + SRSF(f(t)) = sgn(\dot f(t)) \sqrt{|\dot f(t)|} = q(t) This representation it is used to compute the extended non-parametric Fisher-Rao distance between functions, wich under the SRSF representation @@ -137,7 +137,7 @@ def transform(self, X: FDataGrid, y: object = None) -> FDataGrid: .. math:: - SRSF(f(t)) = sgn(f(t)) \sqrt{\dot f(t)|} = q(t) + SRSF(f(t)) = sgn(\dot f(t)) \sqrt{\dot f(t)|} = q(t) Args: X: Functions to be transformed. @@ -166,7 +166,7 @@ def transform(self, X: FDataGrid, y: object = None) -> FDataGrid: # Evaluation with the corresponding interpolation data_matrix = g(output_points)[..., 0] - # SRSF(f) = sign(f) * sqrt|Df| (avoiding multiple allocation) + # SRSF(f) = sign(Df) * sqrt|Df| (avoiding multiple allocation) sign_g = np.sign(data_matrix) data_matrix = np.abs(data_matrix, out=data_matrix) data_matrix = np.sqrt(data_matrix, out=data_matrix) From 2aee87bedd2fcc738275a408dc15f4ef5b218a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Wed, 21 Jun 2023 13:56:02 +0200 Subject: [PATCH 053/192] derivatives: apostrophes instead of dots --- skfda/misc/metrics/_fisher_rao.py | 10 +++++----- skfda/misc/operators/_srvf.py | 6 +++--- skfda/preprocessing/registration/_fisher_rao.py | 4 ++-- skfda/preprocessing/registration/validation.py | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/skfda/misc/metrics/_fisher_rao.py b/skfda/misc/metrics/_fisher_rao.py index 89333ca5a..7fd18e4da 100644 --- a/skfda/misc/metrics/_fisher_rao.py +++ b/skfda/misc/metrics/_fisher_rao.py @@ -61,8 +61,8 @@ class FisherRaoDistance(): .. math:: d_{FR}(f_i, f_j) = \| q_i - q_j \|_2 = - \left ( \int_0^1 sgn(\dot{f_i}(t))\sqrt{|\dot{f_i}(t)|} - - sgn(\dot{f_j}(t))\sqrt{|\dot{f_j}(t)|} dt \right )^{\frac{1}{2}} + \left ( \int_0^1 sgn(f_i'(t))\sqrt{|f_i'(t)|} - + sgn(f_j'(t))\sqrt{|f_j'(t)|} dt \right )^{\frac{1}{2}} If the observations are distributions of random variables the distance will match with the usual Fisher-Rao distance in non-parametric form for @@ -164,7 +164,7 @@ def fisher_rao_amplitude_distance( given by .. math:: - \mathcal{R}(\gamma) = \|\sqrt{\dot{\gamma}}- 1 \|_{\mathbb{L}^2}^2 + \mathcal{R}(\gamma) = \|\sqrt{\gamma'}- 1 \|_{\mathbb{L}^2}^2 See the :footcite:`srivastava+klassen_2016_functionala` for a detailed explanation. @@ -260,7 +260,7 @@ def fisher_rao_phase_distance( .. math:: d_{P}(f_i, f_j) = d_{FR}(\gamma_{ij}, \gamma_{id}) = - arcos \left ( \int_0^1 \sqrt {\dot \gamma_{ij}(t)} dt \right ) + arcos \left ( \int_0^1 \sqrt {\gamma_{ij}'(t)} dt \right ) where :math:`\gamma_{id}` is the identity warping. @@ -346,7 +346,7 @@ def _fisher_rao_warping_distance( .. math:: d_{\Gamma}(\gamma_i, \gamma_j) = cos^{-1} \left ( \int_0^1 - \sqrt{\dot \gamma_i(t)\dot \gamma_j(t)}dt \right ) + \sqrt{\gamma_i'(t)\gamma_j'(t)}dt \right ) See :footcite:`srivastava+klassen_2016_functionala` for a detailed explanation. diff --git a/skfda/misc/operators/_srvf.py b/skfda/misc/operators/_srvf.py index 7764440ea..79234c5ed 100644 --- a/skfda/misc/operators/_srvf.py +++ b/skfda/misc/operators/_srvf.py @@ -24,7 +24,7 @@ class SRSF( function, the SRSF transform is defined as .. math:: - SRSF(f(t)) = sgn(\dot f(t)) \sqrt{|\dot f(t)|} = q(t) + SRSF(f(t)) = sgn(f'(t)) \sqrt{|f'(t)|} = q(t) This representation it is used to compute the extended non-parametric Fisher-Rao distance between functions, wich under the SRSF representation @@ -137,7 +137,7 @@ def transform(self, X: FDataGrid, y: object = None) -> FDataGrid: .. math:: - SRSF(f(t)) = sgn(\dot f(t)) \sqrt{\dot f(t)|} = q(t) + SRSF(f(t)) = sgn(f'(t)) \sqrt{f'(t)|} = q(t) Args: X: Functions to be transformed. @@ -166,7 +166,7 @@ def transform(self, X: FDataGrid, y: object = None) -> FDataGrid: # Evaluation with the corresponding interpolation data_matrix = g(output_points)[..., 0] - # SRSF(f) = sign(Df) * sqrt|Df| (avoiding multiple allocation) + # SRSF(f) = sign(f') * sqrt|f'| (avoiding multiple allocation) sign_g = np.sign(data_matrix) data_matrix = np.abs(data_matrix, out=data_matrix) data_matrix = np.sqrt(data_matrix, out=data_matrix) diff --git a/skfda/preprocessing/registration/_fisher_rao.py b/skfda/preprocessing/registration/_fisher_rao.py index dd43c8bfe..9c630571f 100644 --- a/skfda/preprocessing/registration/_fisher_rao.py +++ b/skfda/preprocessing/registration/_fisher_rao.py @@ -42,13 +42,13 @@ class FisherRaoElasticRegistration( .. math:: d_{\lambda}^2(f \circ \gamma, g) = \| SRSF(f \circ \gamma) - \sqrt{\dot{\gamma}} - SRSF(g)\|_{\mathbb{L}^2}^2 + \lambda + \sqrt{\gamma'} - SRSF(g)\|_{\mathbb{L}^2}^2 + \lambda \mathcal{R}(\gamma) In the implementation it is used as penalty term .. math:: - \mathcal{R}(\gamma) = \|\sqrt{\dot{\gamma}}- 1 \|_{\mathbb{L}^2}^2 + \mathcal{R}(\gamma) = \|\sqrt{\gamma'}- 1 \|_{\mathbb{L}^2}^2 Wich restrict the amount of elasticity employed in the alignment. diff --git a/skfda/preprocessing/registration/validation.py b/skfda/preprocessing/registration/validation.py index aa5d9056f..a948d1350 100644 --- a/skfda/preprocessing/registration/validation.py +++ b/skfda/preprocessing/registration/validation.py @@ -464,12 +464,12 @@ class SobolevLeastSquares(RegistrationScorer[FData, FData]): [S11-5-2-3]_: .. math:: - sls=1 - \frac{\sum_{i=1}^{N} \int\left(\dot{\tilde{f}}_{i}(t)- - \frac{1}{N} \sum_{j=1}^{N} \dot{\tilde{f}}_{j}\right)^{2} dt} - {\sum_{i=1}^{N} \int\left(\dot{f}_{i}(t)-\frac{1}{N} \sum_{j=1}^{N} - \dot{f}_{j}\right)^{2} dt} + sls=1 - \frac{\sum_{i=1}^{N} \int\left(\tilde{f}_i'(t)- + \frac{1}{N} \sum_{j=1}^{N} \tilde{f}_j'\right)^{2} dt} + {\sum_{i=1}^{N} \int\left(f_i'(t)-\frac{1}{N} \sum_{j=1}^{N} + f_j'\right)^{2} dt} - where :math:`\dot{f}_i` and :math:`\dot{\tilde{f}}_i` are the derivatives + where :math:`f_i'` and :math:`\tilde{f}_i'` are the derivatives of the original and the registered data respectively. This criterion measures the total cross-sectional variance of the From 73a9c79db01209045ee7dfa1d21559c9cdea658e Mon Sep 17 00:00:00 2001 From: David del Val Date: Wed, 21 Jun 2023 22:45:16 +0200 Subject: [PATCH 054/192] Use constant weights in linear_differential_operator --- .../misc/operators/_linear_differential_operator.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 0fda06845..dd3594c62 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -591,6 +591,8 @@ def _optimized_operator_evaluation_in_grid( i-th point and 0 in the rest of points. Returns: + If the linear operator does not have constant weights, NotImplemented. + Otherwise, returns a tuple with: - A matrix where the i-th row contians the values of the linear operator applied to the i-th basis function of the delta basis. @@ -642,9 +644,13 @@ def _optimized_operator_evaluation_in_grid( ], ).astype(int) + linear_operator_weights = linear_operator.constant_weights() + if linear_operator_weights is None: + return NotImplemented, NotImplemented + # The desired matrix can be obtained by appending the coefficients # of the finite differences for each point column-wise. - for dif_order, w in enumerate(linear_operator.weights): + for dif_order, w in enumerate(linear_operator_weights): if w == 0: continue for point_index in range(n_points): @@ -711,6 +717,11 @@ def gridbasis_penalty_matrix_optimized( findiff_accuracy=findiff_accuracy, ) + # If the operator does not have constant weights, the + # optimized evaluation is not implemented. + if evaluated_basis is NotImplemented: + return NotImplemented + # The support of the result of applying the linear operator # is a small subset of the grid points. We calculate the # maximum number of points to the left and right of the From 7b480c4972e40ec1b53d927aa3a6123bd9e6086f Mon Sep 17 00:00:00 2001 From: David del Val Date: Wed, 21 Jun 2023 22:45:43 +0200 Subject: [PATCH 055/192] Fix typo --- skfda/misc/operators/_linear_differential_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index dd3594c62..85afdcb79 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -593,7 +593,7 @@ def _optimized_operator_evaluation_in_grid( Returns: If the linear operator does not have constant weights, NotImplemented. Otherwise, returns a tuple with: - - A matrix where the i-th row contians the values of the linear operator + - A matrix where the i-th row contains the values of the linear operator applied to the i-th basis function of the delta basis. - The domain limits of each basis function of the delta basis. Outside From 1ec886ed3c97f24bdd1abb8b18dd11b47d9f3422 Mon Sep 17 00:00:00 2001 From: David del Val Date: Wed, 21 Jun 2023 23:25:03 +0200 Subject: [PATCH 056/192] Tidy grid basis implementation --- skfda/representation/basis/_grid_basis.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/skfda/representation/basis/_grid_basis.py b/skfda/representation/basis/_grid_basis.py index 2533cbc02..4db0238d3 100644 --- a/skfda/representation/basis/_grid_basis.py +++ b/skfda/representation/basis/_grid_basis.py @@ -39,7 +39,7 @@ def __init__( domain_range = tuple((s[0], s[-1]) for s in self.grid_points) super().__init__( domain_range=domain_range, - n_basis=len(grid_points[0]), + n_basis=len(self.grid_points[0]), ) def _evaluate(self, eval_points: NDArrayFloat) -> NDArrayFloat: @@ -54,10 +54,7 @@ def __eq__(self, other: Any) -> bool: ) def __repr__(self) -> str: - return ( - f"{type(self).__name__}(" - f"grid_points={self.grid_points}, " - ) + return f"{type(self).__name__}(grid_points={self.grid_points}) " def __hash__(self) -> int: return ( From ff237776b191eace5cbd04a3da9b60d0bd89ba9f Mon Sep 17 00:00:00 2001 From: David del Val Date: Wed, 21 Jun 2023 23:25:11 +0200 Subject: [PATCH 057/192] Change grid basis hash calculation --- skfda/representation/basis/_grid_basis.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/skfda/representation/basis/_grid_basis.py b/skfda/representation/basis/_grid_basis.py index 4db0238d3..28ac5330a 100644 --- a/skfda/representation/basis/_grid_basis.py +++ b/skfda/representation/basis/_grid_basis.py @@ -57,9 +57,4 @@ def __repr__(self) -> str: return f"{type(self).__name__}(grid_points={self.grid_points}) " def __hash__(self) -> int: - return ( - super().__hash__() - ^ hash( - self.grid_points, - ) - ) + return hash((super(), f"{self.grid_points}")) From 56fe4debba08c93976f15c956f6877a61c7d2065 Mon Sep 17 00:00:00 2001 From: David del Val Date: Wed, 21 Jun 2023 23:27:40 +0200 Subject: [PATCH 058/192] Add missing empty line between methods --- skfda/misc/operators/_linear_differential_operator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skfda/misc/operators/_linear_differential_operator.py b/skfda/misc/operators/_linear_differential_operator.py index 85afdcb79..9af02f4c1 100644 --- a/skfda/misc/operators/_linear_differential_operator.py +++ b/skfda/misc/operators/_linear_differential_operator.py @@ -812,6 +812,7 @@ def fdatagrid_penalty_matrix_optimized( ) return inner_product_matrix(fdatagrid_evaluated) + @gram_matrix_optimization.register def custombasis_penalty_matrix_optimized( linear_operator: LinearDifferentialOperator, From 0a2a6d478f79482ce6f4578443998129319ed40f Mon Sep 17 00:00:00 2001 From: David del Val Date: Thu, 22 Jun 2023 21:21:48 +0200 Subject: [PATCH 059/192] Change type of axis in mean method to accept integers. As the documention specifies, axis can be either None or 0. --- skfda/representation/_functional_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index ff0bb02b3..ee96248be 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -822,7 +822,7 @@ def sum( # noqa: WPS125 def mean( self: T, *, - axis: None = None, + axis: Optional[int] = None, dtype: None = None, out: None = None, keepdims: bool = False, From f69db8fe8f1a382265e3d18fe6cb3f921ffcf8c0 Mon Sep 17 00:00:00 2001 From: David del Val Date: Fri, 23 Jun 2023 18:03:28 +0200 Subject: [PATCH 060/192] Use tuples in the hash calculation --- skfda/representation/basis/_grid_basis.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/skfda/representation/basis/_grid_basis.py b/skfda/representation/basis/_grid_basis.py index 28ac5330a..76c4e7111 100644 --- a/skfda/representation/basis/_grid_basis.py +++ b/skfda/representation/basis/_grid_basis.py @@ -57,4 +57,12 @@ def __repr__(self) -> str: return f"{type(self).__name__}(grid_points={self.grid_points}) " def __hash__(self) -> int: - return hash((super(), f"{self.grid_points}")) + return hash( + ( + super(), + ( + tuple(grid_point_axis) + for grid_point_axis in self.grid_points + ), + ), + ) From b81b34b5e1bb69cc678b867374a1eb7188196704 Mon Sep 17 00:00:00 2001 From: David del Val Date: Sat, 24 Jun 2023 14:40:24 +0200 Subject: [PATCH 061/192] Change to int | None --- skfda/representation/_functional_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index ee96248be..daccb963e 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -786,7 +786,7 @@ def copy( def sum( # noqa: WPS125 self: T, *, - axis: Optional[int] = None, + axis: int | None = None, out: None = None, keepdims: bool = False, skipna: bool = False, From 99906d8955b89524143068b99e3f80d8f1076279 Mon Sep 17 00:00:00 2001 From: David del Val Date: Sat, 24 Jun 2023 14:43:04 +0200 Subject: [PATCH 062/192] Change to int | None --- skfda/representation/_functional_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index daccb963e..ca141aded 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -822,7 +822,7 @@ def sum( # noqa: WPS125 def mean( self: T, *, - axis: Optional[int] = None, + axis: int | None = None, dtype: None = None, out: None = None, keepdims: bool = False, From fcb5ffe224d443a09815f99f13231a6cdeab38d2 Mon Sep 17 00:00:00 2001 From: David del Val Date: Sat, 24 Jun 2023 18:20:19 +0200 Subject: [PATCH 063/192] Test different mypy command --- .github/workflows/mypy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index ab848718b..3c2d690ce 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -16,5 +16,6 @@ jobs: reporter: github-pr-review # Change reporter level if you need. # GitHub Status Check won't become failure with warning. - level: warning + level: error + setup_command: pip install -r requirements.txt pytest mypy mypy_flags: '' From d293fc3717d557bd623929d045a1016f4843e548 Mon Sep 17 00:00:00 2001 From: David del Val Date: Sat, 24 Jun 2023 18:30:23 +0200 Subject: [PATCH 064/192] Update comments --- .github/workflows/mypy.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 3c2d690ce..b3f5bf9af 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -12,10 +12,8 @@ jobs: - uses: tsuyoshicho/action-mypy@v3 with: github_token: ${{ secrets.github_token }} - # Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review]. reporter: github-pr-review - # Change reporter level if you need. - # GitHub Status Check won't become failure with warning. + # The action will output fail if there are mypy errors level: error setup_command: pip install -r requirements.txt pytest mypy mypy_flags: '' From eb6597055319b183998c724c8d28bbe5e87827cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20S=C3=A1nchez=20Signorini?= Date: Sat, 24 Jun 2023 20:17:08 +0200 Subject: [PATCH 065/192] Positional Arguments fixed --- skfda/representation/_functional_data.py | 8 +++++--- skfda/representation/basis/_fdatabasis.py | 8 +++++--- skfda/representation/grid.py | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index 8d35811b0..b067e096d 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -823,23 +823,25 @@ def sum( # noqa: WPS125 @overload def cov( # noqa: WPS451 self: T, - /, s_points: NDArrayFloat, t_points: NDArrayFloat, + /, ) -> NDArrayFloat: pass @overload - def cov( + def cov( # noqa: WPS451 self: T, + /, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @abstractmethod - def cov( # noqa: WPS320 + def cov( # noqa: WPS320, WPS451 self: T, s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, + /, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 670bf6e50..4443f9ca4 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -466,22 +466,24 @@ def var(self: T, eval_points: Optional[NDArrayFloat] = None) -> T: @overload def cov( # noqa: WPS451 self: T, - /, s_points: NDArrayFloat, t_points: NDArrayFloat, + /, ) -> NDArrayFloat: pass @overload - def cov( + def cov( # noqa: WPS451 self: T, + /, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass - def cov( # noqa: WPS320 + def cov( # noqa: WPS320, WPS451 self: T, s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, + /, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 4ff68af99..002d2dc2b 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -598,22 +598,24 @@ def var(self: T) -> T: @overload def cov( # noqa: WPS451 self: T, - /, s_points: NDArrayFloat, t_points: NDArrayFloat, + /, ) -> NDArrayFloat: pass @overload - def cov( + def cov( # noqa: WPS451 self: T, + /, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass - def cov( # noqa: WPS320 + def cov( # noqa: WPS320, WPS451 self: T, s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, + /, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, From 98273faaa0e1ef0dded2125496fc1e6b86d28bb2 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 27 Jun 2023 12:55:12 +0200 Subject: [PATCH 066/192] Added KNeighborsTransformer, without docs for the moment. --- skfda/{ml => _utils}/_neighbors_base.py | 0 .../exploratory/outliers/neighbors_outlier.py | 2 +- .../classification/_neighbors_classifiers.py | 10 +- skfda/ml/clustering/_neighbors_clustering.py | 10 +- skfda/ml/regression/_neighbors_regression.py | 10 +- skfda/preprocessing/dim_reduction/__init__.py | 2 + .../dim_reduction/_neighbor_transforms.py | 104 ++++++++++++++++++ skfda/tests/test_neighbors.py | 34 ++++++ 8 files changed, 156 insertions(+), 16 deletions(-) rename skfda/{ml => _utils}/_neighbors_base.py (100%) create mode 100644 skfda/preprocessing/dim_reduction/_neighbor_transforms.py diff --git a/skfda/ml/_neighbors_base.py b/skfda/_utils/_neighbors_base.py similarity index 100% rename from skfda/ml/_neighbors_base.py rename to skfda/_utils/_neighbors_base.py diff --git a/skfda/exploratory/outliers/neighbors_outlier.py b/skfda/exploratory/outliers/neighbors_outlier.py index df1bd024a..5243a1356 100644 --- a/skfda/exploratory/outliers/neighbors_outlier.py +++ b/skfda/exploratory/outliers/neighbors_outlier.py @@ -7,8 +7,8 @@ from sklearn.neighbors import LocalOutlierFactor as _LocalOutlierFactor from typing_extensions import Literal +from ..._utils._neighbors_base import AlgorithmType, KNeighborsMixin from ...misc.metrics import PairwiseMetric, l2_distance -from ...ml._neighbors_base import AlgorithmType, KNeighborsMixin from ...representation import FData from ...typing._metric import Metric from ...typing._numpy import NDArrayFloat, NDArrayInt diff --git a/skfda/ml/classification/_neighbors_classifiers.py b/skfda/ml/classification/_neighbors_classifiers.py index 7f40e8d8e..4827eaae5 100644 --- a/skfda/ml/classification/_neighbors_classifiers.py +++ b/skfda/ml/classification/_neighbors_classifiers.py @@ -10,17 +10,17 @@ ) from typing_extensions import Literal -from ...misc.metrics import l2_distance -from ...representation import FData -from ...typing._metric import Metric -from ...typing._numpy import NDArrayFloat, NDArrayInt -from .._neighbors_base import ( +from ..._utils._neighbors_base import ( AlgorithmType, KNeighborsMixin, NeighborsClassifierMixin, RadiusNeighborsMixin, WeightsType, ) +from ...misc.metrics import l2_distance +from ...representation import FData +from ...typing._metric import Metric +from ...typing._numpy import NDArrayFloat, NDArrayInt InputBound = Union[NDArrayFloat, FData] Input = TypeVar("Input", contravariant=True, bound=InputBound) diff --git a/skfda/ml/clustering/_neighbors_clustering.py b/skfda/ml/clustering/_neighbors_clustering.py index 448799729..46f47531b 100644 --- a/skfda/ml/clustering/_neighbors_clustering.py +++ b/skfda/ml/clustering/_neighbors_clustering.py @@ -5,15 +5,15 @@ from typing_extensions import Literal -from ...misc.metrics import l2_distance -from ...representation import FData -from ...typing._metric import Metric -from ...typing._numpy import NDArrayFloat -from .._neighbors_base import ( +from ..._utils._neighbors_base import ( AlgorithmType, KNeighborsMixin, RadiusNeighborsMixin, ) +from ...misc.metrics import l2_distance +from ...representation import FData +from ...typing._metric import Metric +from ...typing._numpy import NDArrayFloat InputBound = Union[NDArrayFloat, FData] Input = TypeVar("Input", contravariant=True, bound=InputBound) diff --git a/skfda/ml/regression/_neighbors_regression.py b/skfda/ml/regression/_neighbors_regression.py index 256bd904a..6249a0d9b 100644 --- a/skfda/ml/regression/_neighbors_regression.py +++ b/skfda/ml/regression/_neighbors_regression.py @@ -10,17 +10,17 @@ ) from typing_extensions import Literal -from ...misc.metrics import l2_distance -from ...representation import FData -from ...typing._metric import Metric -from ...typing._numpy import NDArrayFloat, NDArrayInt -from .._neighbors_base import ( +from ..._utils._neighbors_base import ( AlgorithmType, KNeighborsMixin, NeighborsRegressorMixin, RadiusNeighborsMixin, WeightsType, ) +from ...misc.metrics import l2_distance +from ...representation import FData +from ...typing._metric import Metric +from ...typing._numpy import NDArrayFloat, NDArrayInt InputBound = Union[NDArrayFloat, FData] Input = TypeVar("Input", contravariant=True, bound=InputBound) diff --git a/skfda/preprocessing/dim_reduction/__init__.py b/skfda/preprocessing/dim_reduction/__init__.py index 79df95a6b..b0e01058d 100644 --- a/skfda/preprocessing/dim_reduction/__init__.py +++ b/skfda/preprocessing/dim_reduction/__init__.py @@ -13,11 +13,13 @@ ], submod_attrs={ "_fpca": ["FPCA"], + "_neighbor_transforms": ["KNeighborsTransformer"] }, ) if TYPE_CHECKING: from ._fpca import FPCA as FPCA + from ._neighbor_transforms import KNeighborsTransformer as KNeighborsTransformer def __getattr__(name: str) -> Any: diff --git a/skfda/preprocessing/dim_reduction/_neighbor_transforms.py b/skfda/preprocessing/dim_reduction/_neighbor_transforms.py new file mode 100644 index 000000000..634bfed27 --- /dev/null +++ b/skfda/preprocessing/dim_reduction/_neighbor_transforms.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +from typing import Any, Literal, TypeVar, Union, overload + +from scipy.sparse import csr_matrix +from sklearn.neighbors import KNeighborsTransformer as _KNeighborsTransformer + +from skfda._utils._sklearn_adapter import InductiveTransformerMixin + +from ..._utils._neighbors_base import AlgorithmType, KNeighborsMixin +from ...misc.metrics import l2_distance +from ...representation import FData +from ...typing._metric import Metric +from ...typing._numpy import NDArrayFloat + +InputBound = Union[NDArrayFloat, FData] +Input = TypeVar("Input", contravariant=True, bound=InputBound) + + +class KNeighborsTransformer( + KNeighborsMixin[Input, Any], + InductiveTransformerMixin[Input, NDArrayFloat, Any], +): + + @overload + def __init__( + self: KNeighborsTransformer[NDArrayFloat], + *, + mode: Literal["connectivity", "distance"] = "distance", + n_neighbors: int = 5, + algorithm: AlgorithmType = 'auto', + leaf_size: int = 30, + metric: Literal["precomputed"], + n_jobs: int | None = None, + ) -> None: + pass + + @overload + def __init__( + self: KNeighborsTransformer[InputBound], + *, + mode: Literal["connectivity", "distance"] = "distance", + n_neighbors: int = 5, + algorithm: AlgorithmType = 'auto', + leaf_size: int = 30, + n_jobs: int | None = None, + ) -> None: + pass + + @overload + def __init__( + self, + *, + mode: Literal["connectivity", "distance"] = "distance", + n_neighbors: int = 5, + algorithm: AlgorithmType = 'auto', + leaf_size: int = 30, + metric: Metric[Input] = l2_distance, + n_jobs: int | None = None, + ) -> None: + pass + + # Not useless, it restricts parameters + def __init__( # noqa: WPS612 + self, + *, + mode: Literal["connectivity", "distance"] = "distance", + n_neighbors: int = 5, + algorithm: AlgorithmType = 'auto', + leaf_size: int = 30, + metric: Literal["precomputed"] | Metric[Input] = l2_distance, + n_jobs: int | None = None, + ) -> None: + self.mode = mode + super().__init__( + n_neighbors=n_neighbors, + algorithm=algorithm, + leaf_size=leaf_size, + metric=metric, + n_jobs=n_jobs, + ) + + def _init_estimator(self) -> _KNeighborsTransformer: + + return _KNeighborsTransformer( + mode=self.mode, + n_neighbors=self.n_neighbors, + algorithm=self.algorithm, + leaf_size=self.leaf_size, + metric="precomputed", + n_jobs=self.n_jobs, + ) + + def transform( + self, + X: Input, + ) -> csr_matrix: + self._check_is_fitted() + add_one = self.mode == "distance" + return self.kneighbors_graph( + X, + mode=self.mode, + n_neighbors=self.n_neighbors + add_one, + ) diff --git a/skfda/tests/test_neighbors.py b/skfda/tests/test_neighbors.py index 9463d6d09..37ffa941b 100644 --- a/skfda/tests/test_neighbors.py +++ b/skfda/tests/test_neighbors.py @@ -6,6 +6,7 @@ import numpy as np from sklearn.neighbors._base import KNeighborsMixin, RadiusNeighborsMixin +from sklearn.pipeline import Pipeline from skfda.datasets import make_multimodal_samples, make_sinusoidal_process from skfda.exploratory.outliers import LocalOutlierFactor # Pending theory @@ -16,6 +17,7 @@ ) from skfda.ml.clustering import NearestNeighbors from skfda.ml.regression import KNeighborsRegressor, RadiusNeighborsRegressor +from skfda.preprocessing.dim_reduction import KNeighborsTransformer from skfda.representation import FDataBasis, FDataGrid from skfda.representation.basis import FourierBasis @@ -87,6 +89,38 @@ def test_predict_classifier(self) -> None: err_msg=f'fail in {type(neigh)}', ) + def test_predict_classifier_transformer_knn(self) -> None: + """Tests equivalence between using the knn transformer or not.""" + n_neighbors_list = range(1, 11, 2) + + for n_neighbors in n_neighbors_list: + classifier = KNeighborsClassifier(n_neighbors=n_neighbors) + transformer_classifier = Pipeline([ + ( + "transformer", + KNeighborsTransformer( + n_neighbors=max(n_neighbors_list), + mode="distance", + ), + ), + ( + "classifier", + KNeighborsClassifier( + n_neighbors=n_neighbors, + metric="precomputed", + ), + ), + ]) + + classifier.fit(self.X, self.y) + transformer_classifier.fit(self.X, self.y) + pred_classifier = classifier.predict(self.X) + pred_transformer = transformer_classifier.predict(self.X) + np.testing.assert_allclose( + pred_classifier, + pred_transformer, + ) + def test_predict_proba_classifier(self) -> None: """Tests predict proba for k neighbors classifier.""" neigh = KNeighborsClassifier(metric=l2_distance) From eab8f2c69c1a40af51d8637632485a55089a6a64 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 27 Jun 2023 15:41:11 +0200 Subject: [PATCH 067/192] Cap Scipy version in tests to prevent errors until a proper fix. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 19dcf600c..dc5ea6708 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ test = [ "pytest", "pytest-env", "pytest-subtests", + "scipy<1.11.0", ] [project.urls] From c53f0d37fab8c189c3728a9d799280c9d235b595 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 27 Jun 2023 16:53:48 +0200 Subject: [PATCH 068/192] Accept arrays passing through the evaluation transformer. --- .../_evaluation_trasformer.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/skfda/preprocessing/feature_construction/_evaluation_trasformer.py b/skfda/preprocessing/feature_construction/_evaluation_trasformer.py index 43991208d..c9382ddde 100644 --- a/skfda/preprocessing/feature_construction/_evaluation_trasformer.py +++ b/skfda/preprocessing/feature_construction/_evaluation_trasformer.py @@ -144,10 +144,14 @@ def __init__( def fit( # noqa: D102 self: SelfType, - X: FData, + X: FData | NDArrayFloat, y: object = None, ) -> SelfType: - if self.eval_points is None and not isinstance(X, FDataGrid): + if ( + callable(X) + and self.eval_points is None + and not isinstance(X, FDataGrid) + ): raise ValueError( "If no eval_points are passed, the functions " "should be FDataGrid objects.", @@ -159,19 +163,22 @@ def fit( # noqa: D102 def transform( # noqa: D102 self, - X: FData, + X: FData | NDArrayFloat, y: object = None, ) -> NDArrayFloat: check_is_fitted(self, '_is_fitted') - if self.eval_points is None: - assert isinstance(X, FDataGrid) - evaluation = X.data_matrix.copy() + if callable(X): + if self.eval_points is None: + assert isinstance(X, FDataGrid) + evaluation = X.data_matrix.copy() + else: + evaluation = X( + self.eval_points, + extrapolation=self.extrapolation, + grid=self.grid, + ) else: - evaluation = X( - self.eval_points, - extrapolation=self.extrapolation, - grid=self.grid, - ) + evaluation = X - return evaluation.reshape((X.n_samples, -1)) + return evaluation.reshape((len(X), -1)) From 706db429914255cf2093150aab8a722519c1e592 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 27 Jun 2023 17:52:46 +0200 Subject: [PATCH 069/192] Store number of features for now. --- .../dim_reduction/_neighbor_transforms.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/skfda/preprocessing/dim_reduction/_neighbor_transforms.py b/skfda/preprocessing/dim_reduction/_neighbor_transforms.py index 634bfed27..c3fe9af2b 100644 --- a/skfda/preprocessing/dim_reduction/_neighbor_transforms.py +++ b/skfda/preprocessing/dim_reduction/_neighbor_transforms.py @@ -15,6 +15,8 @@ InputBound = Union[NDArrayFloat, FData] Input = TypeVar("Input", contravariant=True, bound=InputBound) +Target = TypeVar("Target") +SelfType = TypeVar("SelfType", bound="KNeighborsTransformer[Any, Any]") class KNeighborsTransformer( @@ -22,6 +24,8 @@ class KNeighborsTransformer( InductiveTransformerMixin[Input, NDArrayFloat, Any], ): + n_neighbors: int + @overload def __init__( self: KNeighborsTransformer[NDArrayFloat], @@ -91,6 +95,17 @@ def _init_estimator(self) -> _KNeighborsTransformer: n_jobs=self.n_jobs, ) + def _fit( + self: SelfType, + X: Input, + y: Target, + fit_with_zeros: bool = True, + ) -> SelfType: + ret = super()._fit(X, y) + self.n_features_in_ = self._estimator.n_features_in_ + + return ret + def transform( self, X: Input, From c7358a4e01801273cbbb9e461fed42fb64c0b92d Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 28 Jun 2023 15:44:02 +0200 Subject: [PATCH 070/192] Fix n_features_in_ in KNeighborsTransformer --- skfda/preprocessing/dim_reduction/_neighbor_transforms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skfda/preprocessing/dim_reduction/_neighbor_transforms.py b/skfda/preprocessing/dim_reduction/_neighbor_transforms.py index c3fe9af2b..0bbd1a488 100644 --- a/skfda/preprocessing/dim_reduction/_neighbor_transforms.py +++ b/skfda/preprocessing/dim_reduction/_neighbor_transforms.py @@ -102,7 +102,8 @@ def _fit( fit_with_zeros: bool = True, ) -> SelfType: ret = super()._fit(X, y) - self.n_features_in_ = self._estimator.n_features_in_ + + self.n_features_in_ = 1 if isinstance(X, FData) else X.shape[1] return ret From c4b2a669378e89d07265e10f756303ca25d3602e Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 28 Jun 2023 19:40:50 +0200 Subject: [PATCH 071/192] Fix error storing criterion in mRMR. --- skfda/preprocessing/dim_reduction/variable_selection/mrmr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/preprocessing/dim_reduction/variable_selection/mrmr.py b/skfda/preprocessing/dim_reduction/variable_selection/mrmr.py index dc9265133..638d93bf3 100644 --- a/skfda/preprocessing/dim_reduction/variable_selection/mrmr.py +++ b/skfda/preprocessing/dim_reduction/variable_selection/mrmr.py @@ -434,7 +434,7 @@ def _validate_parameters(self) -> None: ) if self.criterion == "difference": - self.criterion = operator.sub + self.criterion_ = operator.sub elif self.criterion == "quotient": self.criterion_ = operator.truediv else: From e56b1b4107cc15ac233da3209da13a3a0a2f8706 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Thu, 29 Jun 2023 17:57:10 +0200 Subject: [PATCH 072/192] Fix bug with max_points in MH when the number of maxima is small. --- .../dim_reduction/variable_selection/maxima_hunting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/preprocessing/dim_reduction/variable_selection/maxima_hunting.py b/skfda/preprocessing/dim_reduction/variable_selection/maxima_hunting.py index 0a14609bb..bbac46510 100644 --- a/skfda/preprocessing/dim_reduction/variable_selection/maxima_hunting.py +++ b/skfda/preprocessing/dim_reduction/variable_selection/maxima_hunting.py @@ -101,7 +101,7 @@ def __call__(self, X: FDataGrid) -> NDArrayInt: values = X.data_matrix[:, indexes] partition_indexes = np.argpartition( values, - -self.max_points, + -min(self.max_points, len(values)), axis=None, ) indexes = indexes[np.sort(partition_indexes[-self.max_points:])] From ca14b4dd6389535c2df3672c695fa6ab86e23851 Mon Sep 17 00:00:00 2001 From: David del Val Date: Fri, 30 Jun 2023 23:49:30 +0200 Subject: [PATCH 073/192] Polish fpca regression example --- examples/plot_fpca_regression.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/examples/plot_fpca_regression.py b/examples/plot_fpca_regression.py index 26e54a96c..d1af4463e 100644 --- a/examples/plot_fpca_regression.py +++ b/examples/plot_fpca_regression.py @@ -32,7 +32,7 @@ # curves. The color of these curves depends on the amount of fat, from least # (yellow) to highest (red). -X.plot(gradient_criteria=y, legend=True) +X.plot(gradient_criteria=y, legend=True, colormap="Greens") plt.show() ############################################################################## @@ -53,7 +53,7 @@ reg = FPCARegression(n_components=5) reg.fit(X_train, y_train) -print(reg.score(X_test, y_test)) +print(f"Score with 5 components: {reg.score(X_test, y_test):.4f}") ############################################################################## # We have obtained a pretty good result considering that @@ -73,7 +73,7 @@ print("Best params:", gscv.best_params_) -print("Best cross-validation score:", gscv.best_score_) +print(f"Best cross-validation score: {gscv.best_score_:.4f}") ############################################################################## # The best performance for the train set is obtained using 30 components. @@ -87,24 +87,29 @@ fig = plt.figure() ax = fig.add_subplot(1, 1, 1) -ax.bar(param_grid["n_components"], gscv.cv_results_["mean_test_score"]) +ax.plot( + param_grid["n_components"], + gscv.cv_results_["mean_test_score"], + linestyle="dashed", + marker="o", +) ax.set_xticks(range(0, 100, 10)) -ax.set_ylabel("Number of Components") -ax.set_xlabel("Cross-validation score") +ax.set_xlabel("Number of Components") +ax.set_ylabel("Cross-validation score") ax.set_ylim((0.5, 1)) +fig.show() ############################################################################## # To conclude, we can calculate the score of the model on the test set after -# it has been trained on the whole train set. As expected, the score is -# slightly higher than the one reported by the cross-validation. +# it has been trained on the whole train set. # # Moreover, we can check that the score barely changes when we use a somewhat # smaller number of components. reg = FPCARegression(n_components=30) reg.fit(X_train, y_train) -print("Score with 30 components:", reg.score(X_test, y_test)) +print(f"Score with 30 components: {reg.score(X_test, y_test):.4f}") reg = FPCARegression(n_components=15) reg.fit(X_train, y_train) -print("Score with 15 components:", reg.score(X_test, y_test)) +print(f"Score with 15 components: {reg.score(X_test, y_test):.4f}") From ebeb32fb7d7a596a46979a892ddeebec41851d34 Mon Sep 17 00:00:00 2001 From: David del Val Date: Sat, 1 Jul 2023 00:02:32 +0200 Subject: [PATCH 074/192] Fix x ticks in fpca regression CV plot --- examples/plot_fpca_regression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plot_fpca_regression.py b/examples/plot_fpca_regression.py index d1af4463e..f01974e55 100644 --- a/examples/plot_fpca_regression.py +++ b/examples/plot_fpca_regression.py @@ -93,7 +93,7 @@ linestyle="dashed", marker="o", ) -ax.set_xticks(range(0, 100, 10)) +ax.set_xticks(range(0, 110, 10)) ax.set_xlabel("Number of Components") ax.set_ylabel("Cross-validation score") ax.set_ylim((0.5, 1)) From 6cce69966e6ea0d1e593a01a19fda4c933a8f828 Mon Sep 17 00:00:00 2001 From: David del Val Date: Sat, 1 Jul 2023 00:14:41 +0200 Subject: [PATCH 075/192] Add missing parameter to constructor --- skfda/preprocessing/smoothing/validation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skfda/preprocessing/smoothing/validation.py b/skfda/preprocessing/smoothing/validation.py index ce45bd7ae..1bb0bf2e8 100644 --- a/skfda/preprocessing/smoothing/validation.py +++ b/skfda/preprocessing/smoothing/validation.py @@ -331,6 +331,7 @@ def __init__( return_train_score=False, ) self.param_values = param_values + self.param_name = param_name def fit( # noqa: D102 self, From 9c19d28e75921e5175c62801f87a239e8f88926e Mon Sep 17 00:00:00 2001 From: David del Val Date: Sun, 2 Jul 2023 12:47:45 +0200 Subject: [PATCH 076/192] Fix long f-strings --- examples/plot_fpca_regression.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/plot_fpca_regression.py b/examples/plot_fpca_regression.py index f01974e55..f619e3362 100644 --- a/examples/plot_fpca_regression.py +++ b/examples/plot_fpca_regression.py @@ -53,7 +53,8 @@ reg = FPCARegression(n_components=5) reg.fit(X_train, y_train) -print(f"Score with 5 components: {reg.score(X_test, y_test):.4f}") +test_score = reg.score(X_test, y_test) +print(f"Score with 5 components: {test_score:.4f}") ############################################################################## # We have obtained a pretty good result considering that @@ -108,8 +109,10 @@ reg = FPCARegression(n_components=30) reg.fit(X_train, y_train) -print(f"Score with 30 components: {reg.score(X_test, y_test):.4f}") +test_score = reg.score(X_test, y_test) +print(f"Score with 30 components: {test_score:.4f}") reg = FPCARegression(n_components=15) reg.fit(X_train, y_train) -print(f"Score with 15 components: {reg.score(X_test, y_test):.4f}") +test_score = reg.score(X_test, y_test) +print(f"Score with 15 components: {test_score:.4f}") From 4a56dd774b30a8e29fb8b049b409b77cca9a53e1 Mon Sep 17 00:00:00 2001 From: Quentin Grimonprez Date: Mon, 3 Jul 2023 17:58:02 +0200 Subject: [PATCH 077/192] fix IndexError in FPCAPlot --- skfda/exploratory/visualization/fpca.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/skfda/exploratory/visualization/fpca.py b/skfda/exploratory/visualization/fpca.py index 415dba2f7..94f53f561 100644 --- a/skfda/exploratory/visualization/fpca.py +++ b/skfda/exploratory/visualization/fpca.py @@ -84,9 +84,10 @@ def _plot( self.mean = self.mean.mean() for i, ax in enumerate(axes): - perturbations = self._get_component_perturbations(i) - GraphPlot(fdata=perturbations, axes=ax).plot() - ax.set_title(f"Principal component {i + 1}") + if i < self.n_subplots: + perturbations = self._get_component_perturbations(i) + GraphPlot(fdata=perturbations, axes=ax).plot() + ax.set_title(f"Principal component {i + 1}") def _get_component_perturbations(self, index: int = 0) -> FData: """ From 0d63cf2ede66355452b37245dcf09f87815f95da Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 12 Jul 2023 17:47:04 +0200 Subject: [PATCH 078/192] Add parameter `ddof` to `FData.cov` & `stats.cov`. As explained in #555, this parameter has value 1 by default and means "Delta Degrees of Freedom". The divisor used in the calculation of the covariance is `N-ddof`. --- skfda/exploratory/stats/_stats.py | 11 +++++++-- skfda/misc/covariances.py | 27 +++++++++++++++++------ skfda/representation/_functional_data.py | 6 +++++ skfda/representation/basis/_fdatabasis.py | 8 ++++++- skfda/representation/grid.py | 8 ++++++- 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index e3e9050d2..5d24cb154 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -71,7 +71,10 @@ def gmean(X: FDataGrid) -> FDataGrid: return X.gmean() -def cov(X: FData) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: +def cov( + X: FData, + ddof: int = 1 +) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: """ Compute the covariance. @@ -80,13 +83,17 @@ def cov(X: FData) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: Args: X: Object containing different samples of a functional variable. + ddof: "Delta Degrees of Freedom": the divisor used in the calculation + is `N - ddof`, where `N` represents the number of elements. By + default `ddof` is 1. + Returns: Covariance of all the samples in the original object, as a callable. """ - return X.cov() + return X.cov(ddof=ddof) def modified_epigraph_index(X: FDataGrid) -> NDArrayFloat: diff --git a/skfda/misc/covariances.py b/skfda/misc/covariances.py index 2d4d444f0..e74dab770 100644 --- a/skfda/misc/covariances.py +++ b/skfda/misc/covariances.py @@ -768,16 +768,19 @@ class Empirical(Covariance): The sample covariance function is defined as . math:: - K(t, s) = \frac{1}{n}\sum_{n=1}^N\left(x_n(t) - \bar{x}(t)\right) - \left(x_n(s) - \bar{x}(s)\right) + K(t, s) = \frac{1}{N-\text{ddof}}\sum_{n=1}^N\left(x_n(t) - + \bar{x}(t)\right) \left(x_n(s) - \bar{x}(s)\right) where :math:`x_n(t)` is the n-th sample and :math:`\bar{x}(t)` is the - mean of the samples. + mean of the samples. :math:`N` is the number of samples, + :math:`\text{ddof}` means "Delta Degrees of Freedom" and is such that + :math:`N-\text{ddof}` is the divisor used in the calculation of the + covariance function. """ _latex_formula = ( - r"K(t, s) = \frac{1}{n}\sum_{n=1}^N(x_n(t) - \bar{x}(t))" + r"K(t, s) = \frac{1}{N-\text{ddof}}\sum_{n=1}^N(x_n(t) - \bar{x}(t))" r"(x_n(s) - \bar{x}(s))" ) _parameters_str = [ @@ -785,6 +788,7 @@ class Empirical(Covariance): ] cov_fdata: FData + ddof: int @abc.abstractmethod def __init__(self, data: FData) -> None: @@ -811,14 +815,17 @@ class EmpiricalGrid(Empirical): """Sample covariance function for FDataGrid.""" cov_fdata: FDataGrid + ddof: int - def __init__(self, data: FDataGrid) -> None: + def __init__(self, data: FDataGrid, ddof: int = 1) -> None: super().__init__(data=data) + self.ddof = ddof self.cov_fdata = data.copy( data_matrix=np.cov( data.data_matrix[..., 0], rowvar=False, + ddof=ddof, )[np.newaxis, ...], grid_points=[ data.grid_points[0], @@ -844,11 +851,17 @@ class EmpiricalBasis(Empirical): cov_fdata: FDataBasis coeff_matrix: NDArrayFloat + ddof: int - def __init__(self, data: FDataBasis) -> None: + def __init__(self, data: FDataBasis, ddof: int = 1) -> None: super().__init__(data=data) - self.coeff_matrix = np.cov(data.coefficients, rowvar=False) + self.ddof = ddof + self.coeff_matrix = np.cov( + data.coefficients, + rowvar=False, + ddof=ddof, + ) self.cov_fdata = FDataBasis( basis=TensorBasis([data.basis, data.basis]), coefficients=self.coeff_matrix.flatten(), diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index 9b9256eb5..2696e7b25 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -826,6 +826,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, + ddof: int = 1, ) -> NDArrayFloat: pass @@ -833,6 +834,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, + ddof: int = 1, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -842,6 +844,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, + ddof: int = 1, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -861,6 +864,9 @@ def cov( # noqa: WPS320, WPS451 Args: s_points: Points where the covariance function is evaluated. t_points: Points where the covariance function is evaluated. + ddof: "Delta Degrees of Freedom": the divisor used in the + calculation is `N - ddof`, where `N` represents the number + of elements. By default `ddof` is 1. Returns: Covariance function. diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 4443f9ca4..544bb0e49 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -469,6 +469,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, + ddof: int = 1, ) -> NDArrayFloat: pass @@ -476,6 +477,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, + ddof: int = 1, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -484,6 +486,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, + ddof: int = 1, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -505,6 +508,9 @@ def cov( # noqa: WPS320, WPS451 Args: s_points: Points where the covariance function is evaluated. t_points: Points where the covariance function is evaluated. + ddof: "Delta Degrees of Freedom": the divisor used in the + calculation is `N - ddof`, where `N` represents the number + of elements. By default `ddof` is 1. Returns: Covariance function. @@ -512,7 +518,7 @@ def cov( # noqa: WPS320, WPS451 """ # To avoid circular imports from ...misc.covariances import EmpiricalBasis - cov_function = EmpiricalBasis(self) + cov_function = EmpiricalBasis(self, ddof=ddof) if s_points is None or t_points is None: return cov_function return cov_function(s_points, t_points) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 002d2dc2b..1d6d4d235 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -601,6 +601,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, + ddof: int = 1, ) -> NDArrayFloat: pass @@ -608,6 +609,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, + ddof: int = 1, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -616,6 +618,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, + ddof: int = 1, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -631,6 +634,9 @@ def cov( # noqa: WPS320, WPS451 Args: s_points: Grid points where the covariance function is evaluated. t_points: Grid points where the covariance function is evaluated. + ddof: "Delta Degrees of Freedom": the divisor used in the + calculation is `N - ddof`, where `N` represents the number + of elements. By default `ddof` is 1. Returns: Covariance function. @@ -638,7 +644,7 @@ def cov( # noqa: WPS320, WPS451 """ # To avoid circular imports from ..misc.covariances import EmpiricalGrid - cov_function = EmpiricalGrid(self) + cov_function = EmpiricalGrid(self, ddof=ddof) if s_points is None or t_points is None: return cov_function return cov_function(s_points, t_points) From 26c6d22679cf96c7c7ad12dcc2b99324cb62f0ab Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 12 Jul 2023 19:00:32 +0200 Subject: [PATCH 079/192] Add parameter `ddof` to `FData.var` & `stats.var`. As explained in #555, this parameter has value 1 by default and means "Delta Degrees of Freedom". The divisor used in the calculation of the covariance is `N-ddof`. This changes the previous default behavior of `var`, which used `ddof=0` by default. Therefore, changes have been applied to scikit-fda/skfda/misc/scoring.py (Line 77) and scikit-fda/skfda/ml/clustering/_kmeans.py (Line 150), in order to preserve the previous behaviour of those modules. --- skfda/exploratory/stats/_stats.py | 7 +++++-- skfda/misc/scoring.py | 2 +- skfda/ml/clustering/_kmeans.py | 2 +- skfda/representation/basis/_fdatabasis.py | 11 +++++++++-- skfda/representation/grid.py | 13 +++++++++++-- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 5d24cb154..20d2443e2 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -41,19 +41,22 @@ def mean( return (X * weight).sum() -def var(X: FData) -> FDataGrid: +def var(X: FData, ddof: int = 1) -> FDataGrid: """ Compute the variance of a set of samples in a FData object. Args: X: Object containing all the set of samples whose variance is desired. + ddof: "Delta Degrees of Freedom": the divisor used in the calculation + is `N - ddof`, where `N` represents the number of elements. By + default `ddof` is 1. Returns: Variance of all the samples in the original object, as a :term:`functional data object` with just one sample. """ - return X.var() # type: ignore[no-any-return] + return X.var(ddof=ddof) # type: ignore[no-any-return] def gmean(X: FDataGrid) -> FDataGrid: diff --git a/skfda/misc/scoring.py b/skfda/misc/scoring.py index c57902668..419d25c0f 100644 --- a/skfda/misc/scoring.py +++ b/skfda/misc/scoring.py @@ -74,7 +74,7 @@ def _var( from ..exploratory.stats import mean, var if weights is None: - return var(x) + return var(x, ddof=0) return mean( # type: ignore[no-any-return] np.power(x - mean(x, weights=weights), 2), diff --git a/skfda/ml/clustering/_kmeans.py b/skfda/ml/clustering/_kmeans.py index 166feffc6..9d58e068a 100644 --- a/skfda/ml/clustering/_kmeans.py +++ b/skfda/ml/clustering/_kmeans.py @@ -147,7 +147,7 @@ def _check_clustering(self, fdata: Input) -> Input: return fdata def _tolerance(self, fdata: Input) -> float: - variance = fdata.var() + variance = fdata.var(ddof=0) mean_variance = np.mean(variance[0].data_matrix) return float(mean_variance * self.tol) diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 544bb0e49..8289471d4 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -442,7 +442,11 @@ def sum( # noqa: WPS125 sample_names=(None,), ) - def var(self: T, eval_points: Optional[NDArrayFloat] = None) -> T: + def var( + self: T, + eval_points: Optional[NDArrayFloat] = None, + ddof: int = 1, + ) -> T: """Compute the variance of the functional data object. A numerical approach its used. The object its transformed into its @@ -456,12 +460,15 @@ def var(self: T, eval_points: Optional[NDArrayFloat] = None) -> T: numpy.linspace with bounds equal to the ones defined in self.domain_range and the number of points the maximum between 501 and 10 times the number of basis. + ddof: "Delta Degrees of Freedom": the divisor used in the + calculation is `N - ddof`, where `N` represents the number of + elements. By default `ddof` is 1. Returns: Variance of the original object. """ - return self.to_grid(eval_points).var().to_basis(self.basis) + return self.to_grid(eval_points).var(ddof=ddof).to_basis(self.basis) @overload def cov( # noqa: WPS451 diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 1d6d4d235..20f70b47a 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -582,16 +582,25 @@ def sum( # noqa: WPS125 sample_names=(None,), ) - def var(self: T) -> T: + def var(self: T, ddof: int = 1) -> T: """Compute the variance of a set of samples in a FDataGrid object. + Args: + ddof: "Delta Degrees of Freedom": the divisor used in the + calculation is `N - ddof`, where `N` represents the number of + elements. By default `ddof` is 1. + Returns: A FDataGrid object with just one sample representing the variance of all the samples in the original FDataGrid object. """ return self.copy( - data_matrix=np.array([np.var(self.data_matrix, 0)]), + data_matrix=np.array([np.var( + self.data_matrix, + axis=0, + ddof=ddof, + )]), sample_names=("variance",), ) From b4dc5e768cceace80b09923e986373a4979fb7f5 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 18 Jul 2023 16:05:51 +0200 Subject: [PATCH 080/192] First version of a contributor list. --- .all-contributorsrc | 216 ++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTORS.md | 55 +++++++++++ 2 files changed, 271 insertions(+) create mode 100644 .all-contributorsrc create mode 100644 CONTRIBUTORS.md diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 000000000..84f1aa581 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,216 @@ +{ + "projectName": "scikit-fda", + "projectOwner": "GAA-UAM", + "repoType": "github", + "repoHost": "https://github.com", + "files": [ + "CONTRIBUTORS.md" + ], + "imageSize": 100, + "commit": true, + "commitConvention": "angular", + "contributors": [ + { + "login": "", + "name": "Alberto Suárez", + "avatar_url": "https://scholar.googleusercontent.com/citations?view_op=view_photo&user=4X4znJIAAAAJ&citpid=3", + "profile": "https://scholar.google.es/citations?user=4X4znJIAAAAJ&hl=en", + "contributions": [ + "bug", + "doc", + "ideas", + "mentoring", + "projectManagement", + "research" + ] + }, + { + "login": "jltorrecilla", + "name": "José Luis Torrecilla", + "avatar_url": "https://avatars.githubusercontent.com/u/5597899?v=4", + "profile": "https://github.com/jltorrecilla", + "contributions": [ + "bug", + "doc", + "ideas", + "mentoring", + "projectManagement", + "research", + "talk" + ] + }, + { + "login": "vnmabus", + "name": "Carlos Ramos Carreño", + "avatar_url": "https://avatars.githubusercontent.com/u/2364173?v=4", + "profile": "https://github.com/vnmabus", + "contributions": [ + "bug", + "code", + "doc", + "example", + "ideas", + "infra", + "maintenance", + "mentoring", + "projectManagement", + "question", + "research", + "review", + "test", + "tutorial", + "talk" + ] + }, + { + "login": "mcarbajo", + "name": "Miguel Carbajo Berrocal", + "avatar_url": "https://avatars.githubusercontent.com/u/23211688?v=4", + "profile": "https://github.com/mcarbajo", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "infra", + "test" + ] + }, + { + "login": "pablomm", + "name": "Pablo Marcos", + "avatar_url": "https://avatars.githubusercontent.com/u/16774925?v=4", + "profile": "https://github.com/pablomm", + "contributions": [ + "bug", + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] + }, + { + "login": "amandaher", + "name": "amandaher", + "avatar_url": "https://avatars.githubusercontent.com/u/10870521?v=4", + "profile": "https://github.com/amandaher", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] + }, + { + "login": "manso92", + "name": "Pablo", + "avatar_url": "https://avatars.githubusercontent.com/u/2530727?v=4", + "profile": "http://manso92.com/", + "contributions": [ + "bug", + "code", + "doc", + "example", + "ideas", + "infra", + "test" + ] + }, + { + "login": "hzzhyj", + "name": "hzzhyj", + "avatar_url": "https://avatars.githubusercontent.com/u/22401286?v=4", + "profile": "https://github.com/hzzhyj", + "contributions": [ + "bug", + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] + }, + { + "login": "DavidGarciaFer", + "name": "David García Fernández", + "avatar_url": "https://avatars.githubusercontent.com/u/23263497?v=4", + "profile": "https://davidgarciafer.github.io/", + "contributions": [ + "bug", + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] + }, + { + "login": "pedrorponga", + "name": "pedrorponga", + "avatar_url": "https://avatars.githubusercontent.com/u/32200195?v=4", + "profile": "https://github.com/pedrorponga", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] + }, + { + "login": "mellamansanchez", + "name": "mellamansanchez", + "avatar_url": "https://avatars.githubusercontent.com/u/38490771?v=4", + "profile": "https://github.com/mellamansanchez", + "contributions": [ + "bug", + "code", + "doc", + "example", + "ideas", + "test" + ] + }, + { + "login": "ElenaPetrunina", + "name": "ElenaPetrunina", + "avatar_url": "https://avatars.githubusercontent.com/u/61758794?v=4", + "profile": "https://github.com/ElenaPetrunina", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] + }, + { + "login": "", + "name": "Luis Alberto Rodriguez Ramirez", + "avatar_url": "https://verso.mat.uam.es/web/images/phocagallery/Retratos/thumbs/phoca_thumb_l_LAlbertoRodriguez.jpg", + "profile": "https://verso.mat.uam.es/web/index.php/es/directorio/26-pdef/266-rodriguez-ramirez-luis-alberto", + "contributions": [ + "ideas" + ] + }, + { + "login": "", + "name": "Sergio Ruiz Lozano", + "avatar_url": "", + "profile": "", + "contributions": [ + "design" + ] + } + ], + "contributorsPerLine": 7, + "linkToUsage": true +} diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 000000000..36906bd12 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,55 @@ + + +[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors-) + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Alberto Suárez
Alberto Suárez

🐛 📖 🤔 🧑‍🏫 📆 🔬
José Luis Torrecilla
José Luis Torrecilla

🐛 📖 🤔 🧑‍🏫 📆 🔬 📢
Carlos Ramos Carreño
Carlos Ramos Carreño

🐛 💻 📖 💡 🤔 🚇 🚧 🧑‍🏫 📆 💬 🔬 👀 ⚠️ 📢
Miguel Carbajo Berrocal
Miguel Carbajo Berrocal

💻 📖 💡 🤔 🚇 ⚠️
Pablo Marcos
Pablo Marcos

🐛 💻 📖 💡 🤔 🔬 ⚠️
amandaher
amandaher

💻 📖 💡 🤔 🔬 ⚠️
Pablo
Pablo

🐛 💻 📖 💡 🤔 🚇 ⚠️
hzzhyj
hzzhyj

🐛 💻 📖 💡 🤔 🔬 ⚠️
David García Fernández
David García Fernández

🐛 💻 📖 💡 🤔 🔬 ⚠️
pedrorponga
pedrorponga

💻 📖 💡 🤔 🔬 ⚠️
mellamansanchez
mellamansanchez

🐛 💻 📖 💡 🤔 ⚠️
ElenaPetrunina
ElenaPetrunina

💻 📖 💡 🤔 🔬 ⚠️
Luis Alberto Rodriguez Ramirez
Luis Alberto Rodriguez Ramirez

🤔
Sergio Ruiz Lozano
Sergio Ruiz Lozano
🎨
+ + Add your contributions + +
+ + + + + + + + + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file From 4024b45b17036af0d9cccf3d77d28e1583d9f9cf Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 13:21:54 +0200 Subject: [PATCH 081/192] Add @dSerna4 as a contributor --- .all-contributorsrc | 20 +++++++++++++++++--- CONTRIBUTORS.md | 7 +++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 84f1aa581..324e18551 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -8,7 +8,7 @@ ], "imageSize": 100, "commit": true, - "commitConvention": "angular", + "commitConvention": "none", "contributors": [ { "login": "", @@ -204,11 +204,25 @@ { "login": "", "name": "Sergio Ruiz Lozano", - "avatar_url": "", - "profile": "", + "avatar_url": "https://github.com/GAA-UAM/scikit-fda/blob/develop/docs/logos/logo_only/logo_only.png", + "profile": "https://es.linkedin.com/in/sergioruizlozano", "contributions": [ "design" ] + }, + { + "login": "dSerna4", + "name": "dSerna4", + "avatar_url": "https://avatars.githubusercontent.com/u/91683791?v=4", + "profile": "https://github.com/dSerna4", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 36906bd12..54e954622 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -26,7 +26,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d mellamansanchez
mellamansanchez

🐛 💻 📖 💡 🤔 ⚠️ ElenaPetrunina
ElenaPetrunina

💻 📖 💡 🤔 🔬 ⚠️ Luis Alberto Rodriguez Ramirez
Luis Alberto Rodriguez Ramirez

🤔 - Sergio Ruiz Lozano
Sergio Ruiz Lozano
🎨 + Sergio Ruiz Lozano
Sergio Ruiz Lozano

🎨 + + + dSerna4
dSerna4

💻 📖 💡 🤔 🔬 ⚠️ From 29282adb4cb0da293f6a7a47f5ac31df37bcce2d Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 13:23:15 +0200 Subject: [PATCH 082/192] Add @alvaro-castillo as a contributor --- .all-contributorsrc | 14 ++++++++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 324e18551..4b2564205 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -223,6 +223,20 @@ "research", "test" ] + }, + { + "login": "alvaro-castillo", + "name": "Álvaro Castillo", + "avatar_url": "https://avatars.githubusercontent.com/u/47216026?v=4", + "profile": "https://github.com/alvaro-castillo", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 54e954622..708d55237 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -30,6 +30,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d dSerna4
dSerna4

💻 📖 💡 🤔 🔬 ⚠️ + Álvaro Castillo
Álvaro Castillo

💻 📖 💡 🤔 🔬 ⚠️ From d534d672d5b1bd73fc630c8df4d2d1a0d0573e2e Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 13:30:39 +0200 Subject: [PATCH 083/192] Add @rafa9811 as a contributor --- .all-contributorsrc | 16 ++++++++++++++++ CONTRIBUTORS.md | 7 ++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 4b2564205..63e53232e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -160,6 +160,7 @@ "doc", "example", "ideas", + "infra", "research", "test" ] @@ -188,6 +189,7 @@ "doc", "example", "ideas", + "infra", "research", "test" ] @@ -237,6 +239,20 @@ "research", "test" ] + }, + { + "login": "rafa9811", + "name": "Rafa Hidalgo", + "avatar_url": "https://avatars.githubusercontent.com/u/32574570?v=4", + "profile": "https://github.com/rafa9811", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 708d55237..f8efe4c31 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -22,15 +22,16 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d hzzhyj
hzzhyj

🐛 💻 📖 💡 🤔 🔬 ⚠️ David García Fernández
David García Fernández

🐛 💻 📖 💡 🤔 🔬 ⚠️ - pedrorponga
pedrorponga

💻 📖 💡 🤔 🔬 ⚠️ + pedrorponga
pedrorponga

💻 📖 💡 🤔 🚇 🔬 ⚠️ mellamansanchez
mellamansanchez

🐛 💻 📖 💡 🤔 ⚠️ - ElenaPetrunina
ElenaPetrunina

💻 📖 💡 🤔 🔬 ⚠️ + ElenaPetrunina
ElenaPetrunina

💻 📖 💡 🤔 🚇 🔬 ⚠️ Luis Alberto Rodriguez Ramirez
Luis Alberto Rodriguez Ramirez

🤔 Sergio Ruiz Lozano
Sergio Ruiz Lozano

🎨 dSerna4
dSerna4

💻 📖 💡 🤔 🔬 ⚠️ Álvaro Castillo
Álvaro Castillo

💻 📖 💡 🤔 🔬 ⚠️ + Rafa Hidalgo
Rafa Hidalgo

💻 📖 💡 🤔 🔬 ⚠️ From 40d5b23cdd4ccd518e2f0e9306355e50ac1dc057 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 13:31:57 +0200 Subject: [PATCH 084/192] Add @Ddelval as a contributor --- .all-contributorsrc | 15 +++++++++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 63e53232e..d2267ac85 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -253,6 +253,21 @@ "research", "test" ] + }, + { + "login": "Ddelval", + "name": "David del Val", + "avatar_url": "https://avatars.githubusercontent.com/u/44179156?v=4", + "profile": "https://github.com/Ddelval", + "contributions": [ + "bug", + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f8efe4c31..b0ceb71d0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -32,6 +32,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d dSerna4
dSerna4

💻 📖 💡 🤔 🔬 ⚠️ Álvaro Castillo
Álvaro Castillo

💻 📖 💡 🤔 🔬 ⚠️ Rafa Hidalgo
Rafa Hidalgo

💻 📖 💡 🤔 🔬 ⚠️ + David del Val
David del Val

🐛 💻 📖 💡 🤔 🔬 ⚠️ From e697356bbc893c8299a869959d81277e0f7b4951 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 13:33:15 +0200 Subject: [PATCH 085/192] Add @m5signorini as a contributor --- .all-contributorsrc | 14 ++++++++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index d2267ac85..8013511ec 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -268,6 +268,20 @@ "research", "test" ] + }, + { + "login": "m5signorini", + "name": "Martín", + "avatar_url": "https://avatars.githubusercontent.com/u/23235572?v=4", + "profile": "https://github.com/m5signorini", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b0ceb71d0..171e1b072 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-19-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -33,6 +33,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Álvaro Castillo
Álvaro Castillo

💻 📖 💡 🤔 🔬 ⚠️ Rafa Hidalgo
Rafa Hidalgo

💻 📖 💡 🤔 🔬 ⚠️ David del Val
David del Val

🐛 💻 📖 💡 🤔 🔬 ⚠️ + Martín
Martín

💻 📖 💡 🤔 🔬 ⚠️ From 6702a8a0d0d24ed9da4ae675a79ac5ffdab47c49 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 13:33:53 +0200 Subject: [PATCH 086/192] Add @opintosant as a contributor --- .all-contributorsrc | 14 ++++++++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8013511ec..dc34f4f3f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -282,6 +282,20 @@ "research", "test" ] + }, + { + "login": "opintosant", + "name": "Óscar Pinto", + "avatar_url": "https://avatars.githubusercontent.com/u/82827606?v=4", + "profile": "https://github.com/opintosant", + "contributions": [ + "code", + "doc", + "example", + "ideas", + "research", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 171e1b072..3bb7e094a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-19-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-20-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -34,6 +34,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Rafa Hidalgo
Rafa Hidalgo

💻 📖 💡 🤔 🔬 ⚠️ David del Val
David del Val

🐛 💻 📖 💡 🤔 🔬 ⚠️ Martín
Martín

💻 📖 💡 🤔 🔬 ⚠️ + Óscar Pinto
Óscar Pinto

💻 📖 💡 🤔 🔬 ⚠️ From f8b0c72b85471a78ab26c83ff99157186d0056f5 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 13:34:36 +0200 Subject: [PATCH 087/192] Add @pedrog99 as a contributor --- .all-contributorsrc | 11 +++++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index dc34f4f3f..2c2e16438 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -296,6 +296,17 @@ "research", "test" ] + }, + { + "login": "pedrog99", + "name": "pedrog99", + "avatar_url": "https://avatars.githubusercontent.com/u/44478427?v=4", + "profile": "https://github.com/pedrog99", + "contributions": [ + "ideas", + "infra", + "research" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 3bb7e094a..66611fbf5 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-20-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-21-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -35,6 +35,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d David del Val
David del Val

🐛 💻 📖 💡 🤔 🔬 ⚠️ Martín
Martín

💻 📖 💡 🤔 🔬 ⚠️ Óscar Pinto
Óscar Pinto

💻 📖 💡 🤔 🔬 ⚠️ + pedrog99
pedrog99

🤔 🚇 🔬 From 579787381ac542d49b8dd5187184ddbbcc8fd0f7 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 13:37:48 +0200 Subject: [PATCH 088/192] Update contributor list. --- docs/logos/logo_only/logo_only.png | Bin 0 -> 532361 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/logos/logo_only/logo_only.png diff --git a/docs/logos/logo_only/logo_only.png b/docs/logos/logo_only/logo_only.png new file mode 100644 index 0000000000000000000000000000000000000000..106806473d2a64cdf81537c30fc407ec59350b4c GIT binary patch literal 532361 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 z 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 z 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�w~2 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 z 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

({Yf~|1Cz7_e4IErLX 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 L{aYn}+eQ38I#*y~ literal 0 HcmV?d00001 From 6a04631882ae466759c09c41298b6db1f52459d3 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 15:18:13 +0200 Subject: [PATCH 089/192] Add @Clej as a contributor --- .all-contributorsrc | 11 +++++++++++ CONTRIBUTORS.md | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2c2e16438..eb837b2e2 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -307,6 +307,17 @@ "infra", "research" ] + }, + { + "login": "Clej", + "name": "Clément Lejeune", + "avatar_url": "https://avatars.githubusercontent.com/u/54889281?v=4", + "profile": "https://github.com/Clej", + "contributions": [ + "code", + "example", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 66611fbf5..c8951448d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-21-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -37,6 +37,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Óscar Pinto
Óscar Pinto

💻 📖 💡 🤔 🔬 ⚠️ pedrog99
pedrog99

🤔 🚇 🔬 + + Clément Lejeune
Clément Lejeune

💻 💡 ⚠️ + From 6e2133cb93e09285a086df43bd78f0c639f25de3 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 15:18:57 +0200 Subject: [PATCH 090/192] Add @jiduque as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index eb837b2e2..b75d86ee8 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -318,6 +318,15 @@ "example", "test" ] + }, + { + "login": "jiduque", + "name": "Jorge Duque", + "avatar_url": "https://avatars.githubusercontent.com/u/34616214?v=4", + "profile": "https://github.com/jiduque", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c8951448d..1927965c7 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -39,6 +39,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Clément Lejeune
Clément Lejeune

💻 💡 ⚠️ + Jorge Duque
Jorge Duque

💻 From 2bbf53712b3415e5325a5a6b2e663f405cc15a65 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 15:19:32 +0200 Subject: [PATCH 091/192] Add @Saumya-ranjan as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index b75d86ee8..06c3ba1f7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -327,6 +327,15 @@ "contributions": [ "code" ] + }, + { + "login": "Saumya-ranjan", + "name": "Saumyaranjan Parida", + "avatar_url": "https://avatars.githubusercontent.com/u/57477827?v=4", + "profile": "https://saumya-ranjan.github.io/", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1927965c7..63ac3b104 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-24-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -40,6 +40,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Clément Lejeune
Clément Lejeune

💻 💡 ⚠️ Jorge Duque
Jorge Duque

💻 + Saumyaranjan Parida
Saumyaranjan Parida

💻 From 6070477e6967b3b19fa4c4afd5d7825b931e7171 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 15:38:56 +0200 Subject: [PATCH 092/192] Add @ego-thales as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 06c3ba1f7..f48147885 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -336,6 +336,15 @@ "contributions": [ "code" ] + }, + { + "login": "ego-thales", + "name": "ego-thales", + "avatar_url": "https://avatars.githubusercontent.com/u/121242234?v=4", + "profile": "https://github.com/ego-thales", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 63ac3b104..0e78f8dac 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-24-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-25-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -41,6 +41,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Clément Lejeune
Clément Lejeune

💻 💡 ⚠️ Jorge Duque
Jorge Duque

💻 Saumyaranjan Parida
Saumyaranjan Parida

💻 + ego-thales
ego-thales

📖 From e91967b2816305b4efcca866dbe5a50da6ef8c0f Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 15:42:51 +0200 Subject: [PATCH 093/192] Add @jdtuck as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index f48147885..f419b61b3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -345,6 +345,15 @@ "contributions": [ "doc" ] + }, + { + "login": "jdtuck", + "name": "Derek Tucker", + "avatar_url": "https://avatars.githubusercontent.com/u/2325587?v=4", + "profile": "http://research.tetonedge.net/", + "contributions": [ + "question" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0e78f8dac..fc534508b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-25-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-26-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -42,6 +42,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Jorge Duque
Jorge Duque

💻 Saumyaranjan Parida
Saumyaranjan Parida

💻 ego-thales
ego-thales

📖 + Derek Tucker
Derek Tucker

💬 From a6e59060cdd0179d1a2b602756398a9be46d2949 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 15:43:43 +0200 Subject: [PATCH 094/192] Add @Quentin62 as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index f419b61b3..ccfac1359 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -354,6 +354,15 @@ "contributions": [ "question" ] + }, + { + "login": "Quentin62", + "name": "Quentin Grimonprez", + "avatar_url": "https://avatars.githubusercontent.com/u/19777553?v=4", + "profile": "https://github.com/Quentin62", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index fc534508b..05acb091f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-26-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -43,6 +43,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Saumyaranjan Parida
Saumyaranjan Parida

💻 ego-thales
ego-thales

📖 Derek Tucker
Derek Tucker

💬 + Quentin Grimonprez
Quentin Grimonprez

💻 From 95733b3ce8fc8728638efecb01de33e3fdb8617e Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 15:44:30 +0200 Subject: [PATCH 095/192] Add @half-adder as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index ccfac1359..20293d68d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -363,6 +363,15 @@ "contributions": [ "code" ] + }, + { + "login": "half-adder", + "name": "Sean Johnsen", + "avatar_url": "https://avatars.githubusercontent.com/u/10676434?v=4", + "profile": "https://github.com/half-adder", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 05acb091f..5f53a8c59 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-28-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -44,6 +44,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d ego-thales
ego-thales

📖 Derek Tucker
Derek Tucker

💬 Quentin Grimonprez
Quentin Grimonprez

💻 + Sean Johnsen
Sean Johnsen

📖 From 84b9f80462efdf55b71d02319401172cf25d358d Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 15:45:11 +0200 Subject: [PATCH 096/192] Add @pcuestas as a contributor --- .all-contributorsrc | 11 +++++++++++ CONTRIBUTORS.md | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 20293d68d..c91ffeaa0 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -372,6 +372,17 @@ "contributions": [ "doc" ] + }, + { + "login": "pcuestas", + "name": "pcuestas", + "avatar_url": "https://avatars.githubusercontent.com/u/71875712?v=4", + "profile": "https://github.com/pcuestas", + "contributions": [ + "code", + "ideas", + "research" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5f53a8c59..94c3f06fd 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-28-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-29-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -46,6 +46,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Quentin Grimonprez
Quentin Grimonprez

💻 Sean Johnsen
Sean Johnsen

📖 + + pcuestas
pcuestas

💻 🤔 🔬 + From dc840b6d53cbb1e3443857ec82738f14986f6714 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 16:18:24 +0200 Subject: [PATCH 097/192] Some additions. --- .all-contributorsrc | 2 +- CONTRIBUTORS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index c91ffeaa0..fa11a3bc4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -36,7 +36,7 @@ "mentoring", "projectManagement", "research", - "talk" + {"type": "talk", "url": "https://seio2022.confereasy.com/es/trabajos/2111/scikit-fda-analisis-de-datos-funcionales-en-python"} ] }, { diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 94c3f06fd..972eceafa 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -12,7 +12,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Alberto Suárez
Alberto Suárez

🐛 📖 🤔 🧑‍🏫 📆 🔬 - José Luis Torrecilla
José Luis Torrecilla

🐛 📖 🤔 🧑‍🏫 📆 🔬 📢 + José Luis Torrecilla
José Luis Torrecilla

🐛 📖 🤔 🧑‍🏫 📆 🔬 📢 Carlos Ramos Carreño
Carlos Ramos Carreño

🐛 💻 📖 💡 🤔 🚇 🚧 🧑‍🏫 📆 💬 🔬 👀 ⚠️ 📢 Miguel Carbajo Berrocal
Miguel Carbajo Berrocal

💻 📖 💡 🤔 🚇 ⚠️ Pablo Marcos
Pablo Marcos

🐛 💻 📖 💡 🤔 🔬 ⚠️ From 4c9406674f5f251540eb8364a53fc3905b2fc376 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 19 Jul 2023 19:52:08 +0200 Subject: [PATCH 098/192] Add contributors to the documentation. --- THANKS.txt | 14 -------------- docs/conf.py | 1 + docs/contributors.md | 2 ++ docs/index.rst | 4 ++-- readthedocs-requirements.txt | 1 + 5 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 THANKS.txt create mode 100644 docs/contributors.md diff --git a/THANKS.txt b/THANKS.txt deleted file mode 100644 index b7d881a8b..000000000 --- a/THANKS.txt +++ /dev/null @@ -1,14 +0,0 @@ -Alberto Suárez for motivating the project, and for helping with the design and the mathematics. -José Luis Torrecilla for aiding with the design and the statistical background. -Miguel Carbajo Berrocal for the basis and discrete representations of functional data, and the smoothing functions. -Carlos Ramos Carreño for the design, reviews and supervision, and for contributing the datasets module. -Pablo Marcos Manchón for the registration functions, including integration with fdasrsf. -Amanda Hernando Bernabé for visualization and clustering functions. -Pablo Pérez Manso for regression and related utilities. -Yujian Hong for the Functional Principal Component Analysis and related functionalities. -David García Fernandez for implementing ANOVA and Hotelling tests. -Pedro Martín Rodríguez-Ponga Eyriès for implementing several classification methods. -Álvaro Sánchez Romero for improving the visualization methods and adding interactive visualizations. -Elena Petrunina for improving the documentation, and regression functions. -Luis Alberto Rodriguez Ramirez for providing mathematical support. -Sergio Ruiz Lozano for the design of the logo. diff --git a/docs/conf.py b/docs/conf.py index 6dc246c76..80583b65e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,6 +72,7 @@ # Sphinx extensions extensions = [ "jupyter_sphinx", + "myst_parser", "sphinx.ext.autodoc", "sphinx.ext.autodoc.typehints", "sphinx.ext.autosummary", diff --git a/docs/contributors.md b/docs/contributors.md new file mode 100644 index 000000000..3cf0155af --- /dev/null +++ b/docs/contributors.md @@ -0,0 +1,2 @@ +```{include} ../CONTRIBUTORS.md +``` \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index b1cf957ae..55cbe94dc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,6 +35,7 @@ Github you can find more information related to the development of the package. apilist glossary + contributors An exhaustive list of all the contents of the package can be found in the :ref:`genindex`. @@ -86,8 +87,7 @@ All contributions are welcome. You can help this project grow in multiple ways, from creating an issue, reporting an improvement or a bug, to doing a repository fork and creating a pull request to the development branch. The people involved at some point in the development of the package can be -found in the `contributors file -`_. +found in the :doc:`contributors page `. .. Citation -------- diff --git a/readthedocs-requirements.txt b/readthedocs-requirements.txt index 95b748aba..f646304da 100644 --- a/readthedocs-requirements.txt +++ b/readthedocs-requirements.txt @@ -9,5 +9,6 @@ sphinx-gallery pillow setuptools>=41.2 jupyter-sphinx +myst-parser pytest sphinxcontrib-bibtex \ No newline at end of file From 6eada7287e7e1a6098553714cc9a6a06d7b34eef Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sat, 29 Jul 2023 12:05:16 +0200 Subject: [PATCH 099/192] STD for FDataBasis and FDataGrid --- skfda/exploratory/stats/__init__.py | 2 + skfda/exploratory/stats/_stats.py | 63 ++++++++++++++++++++++++++++- skfda/misc/_math.py | 30 ++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/skfda/exploratory/stats/__init__.py b/skfda/exploratory/stats/__init__.py index 0a1f3a6de..345eb30ac 100644 --- a/skfda/exploratory/stats/__init__.py +++ b/skfda/exploratory/stats/__init__.py @@ -19,6 +19,7 @@ "gmean", "mean", "modified_epigraph_index", + "std", "trim_mean", "var", ], @@ -37,6 +38,7 @@ gmean as gmean, mean as mean, modified_epigraph_index as modified_epigraph_index, + std as std, trim_mean as trim_mean, var as var, ) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 20d2443e2..49fb1adbc 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -5,11 +5,12 @@ from typing import Callable, TypeVar, Union import numpy as np +import functools from scipy import integrate from scipy.stats import rankdata from ...misc.metrics._lp_distances import l2_distance -from ...representation import FData, FDataGrid +from ...representation import FData, FDataGrid, FDataBasis from ...typing._metric import Metric from ...typing._numpy import NDArrayFloat from ..depth import Depth, ModifiedBandDepth @@ -76,7 +77,7 @@ def gmean(X: FDataGrid) -> FDataGrid: def cov( X: FData, - ddof: int = 1 + ddof: int = 1, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: """ Compute the covariance. @@ -99,6 +100,64 @@ def cov( return X.cov(ddof=ddof) +@functools.singledispatch +def std(X: F, ddof: int = 1) -> F: + r""" + Compute the standard deviation of all the samples in a FData object. + + .. math:: + \text{std}_X(t) = \sqrt{\frac{1}{N-\text{ddof}} + \sum_{n=1}^{N}{\left(X_n(t) - \overline{X}(t)\right)^2}} + + Args: + X: Object containing all the samples whose standard deviation is + wanted. + ddof: Means "Delta Degrees of Freedom". The divisor used in + calculations is `N - ddof`, where `N` represents the number of + samples in `X`. By default ddof is 1. + + Returns: + Standard deviation of all the samples in the original object, as a + :term:`functional data object` with just one sample. + + """ + raise NotImplementedError("Not implemented for this type") + + +@std.register(FDataGrid) +def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: + return X.copy( + data_matrix=np.std(X.data_matrix, axis=0, ddof=ddof)[np.newaxis, ...], + sample_names=("standard deviation",), + ) + + +@std.register(FDataBasis) +def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: + from ...misc._math import functional_data_object_to_basis + + if X.dim_domain != 1 or X.dim_codomain != 1: + raise NotImplementedError( + "Standard deviation only implemented " + "for univariate functions." + ) + + basis = X.basis + coeff_matrix = np.cov(X.coefficients, rowvar=False, ddof=ddof) + + def std_function(t_points: NDArrayFloat) -> NDArrayFloat: + assert len(t_points) == 1, ( + "Standard deviation function only implemented for " + "one-point-at-a-time evaluations." + ) + basis_evaluation = basis(t_points).reshape((-1, 1)) + return np.sqrt( + basis_evaluation.T @ coeff_matrix @ basis_evaluation + ).reshape((1, -1, 1)) + + return functional_data_object_to_basis(f=std_function, new_basis=X.basis) + + def modified_epigraph_index(X: FDataGrid) -> NDArrayFloat: """ Calculate the Modified Epigraph Index of a FDataGrid. diff --git a/skfda/misc/_math.py b/skfda/misc/_math.py index 4e02de2f8..52fc20e7b 100644 --- a/skfda/misc/_math.py +++ b/skfda/misc/_math.py @@ -677,3 +677,33 @@ def cosine_similarity_matrix( return _clip_cosine( inner_matrix / norm1[:, np.newaxis] / norm2[np.newaxis, :], ) + + +def functional_data_object_to_basis( + f: Callable[[NDArrayFloat], NDArrayFloat] | NDArrayFloat, + new_basis: Basis, +) -> FDataBasis: + """Express a math function as a FDataBasis with a given basis. + + Args: + f: math function. + new_basis: the basis of the output. + + Returns: + FDataBasis: FDataBasis with calculated coefficients and the new + basis. + """ + if isinstance(f, FDataBasis) and f.basis == new_basis: + return f + + inner_prod = inner_product_matrix( + new_basis, + f, + _domain_range=new_basis.domain_range, + ) + + gram_matrix = new_basis.gram_matrix() + + coefs = np.linalg.solve(gram_matrix, inner_prod) + + return FDataBasis(new_basis, coefs.T) From 036028a23e957301c659aac3c6a236d5a4e29da9 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sat, 29 Jul 2023 13:06:24 +0200 Subject: [PATCH 100/192] A test for std(FData). --- skfda/tests/test_stats.py | 67 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/skfda/tests/test_stats.py b/skfda/tests/test_stats.py index 22b74a437..fb10ad025 100644 --- a/skfda/tests/test_stats.py +++ b/skfda/tests/test_stats.py @@ -2,8 +2,17 @@ import numpy as np -from skfda.datasets import fetch_phoneme, fetch_tecator, fetch_weather -from skfda.exploratory.stats import geometric_median, modified_epigraph_index +from skfda.datasets import ( + fetch_phoneme, + fetch_tecator, + fetch_weather, + make_gaussian_process, +) +from skfda.exploratory.stats import ( + geometric_median, modified_epigraph_index, std +) +from skfda.misc.covariances import Gaussian +from skfda.representation.basis import FourierBasis class TestGeometricMedian(unittest.TestCase): @@ -144,3 +153,57 @@ def test_mei(self) -> None: ]), rtol=1e-5, ) + + +class TestStd(unittest.TestCase): + """Test the standard deviation of fuctional data objects.""" + + def _test_std_gaussian_fourier(self, n_basis: int) -> None: + """ + Test standard deviation using + a gaussian processes and a Fourier basis. + """ + start = 0 + stop = 1 + n_features = 1000 + + gaussian_process = make_gaussian_process( + start=start, + stop=stop, + n_samples=100, + n_features=n_features, + mean=0.0, + cov=Gaussian(variance=1, length_scale=0.1), + random_state=0, + ) + fourier_basis = FourierBasis(n_basis=n_basis, domain_range=(0, 1)) + fd = gaussian_process.to_basis(fourier_basis) + + std_fd = std(fd) + grid = np.linspace(start, stop, n_features) + almost_std_fd = std(fd.to_grid(grid)).to_basis(fourier_basis) + + """ + when measuring the closeness between std(fd) and the "expected" value, + we are a bit more lenient in the extremes of the domain; as the way of + projecting the std to the basis is different in each case. + """ + inner_grid_limit = n_features // 10 + inner_grid = grid[inner_grid_limit:-inner_grid_limit] + np.testing.assert_allclose( + std_fd(inner_grid), + almost_std_fd(inner_grid), + rtol=1e-3, + ) + + outer_grid = grid[:inner_grid_limit] + grid[-inner_grid_limit:] + np.testing.assert_allclose( + std_fd(outer_grid), + almost_std_fd(outer_grid), + rtol=1e-2, + ) + + def test_std(self) -> None: + """Test standard deviation.""" + + self._test_std_gaussian_fourier(61) From b2f251a76fb9c4361b292860593a4a07459b0a0f Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 30 Jul 2023 12:58:30 +0200 Subject: [PATCH 101/192] FIX typing issues in function_to_fdatabasis --- skfda/exploratory/stats/_stats.py | 4 ++-- skfda/misc/_math.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 49fb1adbc..3f52a28cf 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -134,7 +134,7 @@ def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: @std.register(FDataBasis) def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: - from ...misc._math import functional_data_object_to_basis + from ...misc._math import function_to_fdatabasis if X.dim_domain != 1 or X.dim_codomain != 1: raise NotImplementedError( @@ -155,7 +155,7 @@ def std_function(t_points: NDArrayFloat) -> NDArrayFloat: basis_evaluation.T @ coeff_matrix @ basis_evaluation ).reshape((1, -1, 1)) - return functional_data_object_to_basis(f=std_function, new_basis=X.basis) + return function_to_fdatabasis(f=std_function, new_basis=X.basis) def modified_epigraph_index(X: FDataGrid) -> NDArrayFloat: diff --git a/skfda/misc/_math.py b/skfda/misc/_math.py index 52fc20e7b..595c9361d 100644 --- a/skfda/misc/_math.py +++ b/skfda/misc/_math.py @@ -679,8 +679,8 @@ def cosine_similarity_matrix( ) -def functional_data_object_to_basis( - f: Callable[[NDArrayFloat], NDArrayFloat] | NDArrayFloat, +def function_to_fdatabasis( + f: Callable[[NDArrayFloat], NDArrayFloat], new_basis: Basis, ) -> FDataBasis: """Express a math function as a FDataBasis with a given basis. From 90742bb464208bef02c474341b79beb5ebe7ce20 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 11:52:24 +0200 Subject: [PATCH 102/192] Add @yfche as a contributor --- .all-contributorsrc | 14 +++++++++++++- CONTRIBUTORS.md | 3 ++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index fa11a3bc4..3f6f45474 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -36,7 +36,10 @@ "mentoring", "projectManagement", "research", - {"type": "talk", "url": "https://seio2022.confereasy.com/es/trabajos/2111/scikit-fda-analisis-de-datos-funcionales-en-python"} + { + "type": "talk", + "url": "https://seio2022.confereasy.com/es/trabajos/2111/scikit-fda-analisis-de-datos-funcionales-en-python" + } ] }, { @@ -383,6 +386,15 @@ "ideas", "research" ] + }, + { + "login": "yfche", + "name": "Yifeng", + "avatar_url": "https://avatars.githubusercontent.com/u/24327462?v=4", + "profile": "https://github.com/yfche", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 972eceafa..9b2d8b13f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-29-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-30-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -48,6 +48,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d pcuestas
pcuestas

💻 🤔 🔬 + Yifeng
Yifeng

🐛 From bbe6cb1514645115753f92fec1f29a698ab37a7e Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 11:54:50 +0200 Subject: [PATCH 103/192] Add @szhang0629 as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3f6f45474..d4e2c494f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -395,6 +395,15 @@ "contributions": [ "bug" ] + }, + { + "login": "szhang0629", + "name": "Shan Zhang", + "avatar_url": "https://avatars.githubusercontent.com/u/36351460?v=4", + "profile": "https://github.com/szhang0629", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9b2d8b13f..e573a3063 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-30-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-31-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -49,6 +49,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d pcuestas
pcuestas

💻 🤔 🔬 Yifeng
Yifeng

🐛 + Shan Zhang
Shan Zhang

🐛 From d130d49cc1db0e09d94309dcbf76ba5be7887fd1 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 11:55:50 +0200 Subject: [PATCH 104/192] Add @JavierHernandezMontes as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index d4e2c494f..fff7c4e85 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -404,6 +404,15 @@ "contributions": [ "bug" ] + }, + { + "login": "JavierHernandezMontes", + "name": "JavierHernandezMontes", + "avatar_url": "https://avatars.githubusercontent.com/u/25748527?v=4", + "profile": "https://github.com/JavierHernandezMontes", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e573a3063..b675372ab 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-31-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-32-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -50,6 +50,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d pcuestas
pcuestas

💻 🤔 🔬 Yifeng
Yifeng

🐛 Shan Zhang
Shan Zhang

🐛 + JavierHernandezMontes
JavierHernandezMontes

🐛 From d272ab524128eeffb296b8e93cb40fb8efadaa61 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 11:57:22 +0200 Subject: [PATCH 105/192] Add @jrash33 as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index fff7c4e85..3349654a7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -413,6 +413,15 @@ "contributions": [ "bug" ] + }, + { + "login": "jrash33", + "name": "Joey Ashcroft", + "avatar_url": "https://avatars.githubusercontent.com/u/46009245?v=4", + "profile": "https://github.com/jrash33", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b675372ab..1b937824d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-32-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-33-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -51,6 +51,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Yifeng
Yifeng

🐛 Shan Zhang
Shan Zhang

🐛 JavierHernandezMontes
JavierHernandezMontes

🐛 + Joey Ashcroft
Joey Ashcroft

🐛 From 8f656713329ae684adcc3dd5547d8957fcd1af9f Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 11:58:54 +0200 Subject: [PATCH 106/192] Add @alejandro-ariza as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3349654a7..166c88df7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -422,6 +422,15 @@ "contributions": [ "bug" ] + }, + { + "login": "alejandro-ariza", + "name": "Alejandro Ariza", + "avatar_url": "https://avatars.githubusercontent.com/u/38434631?v=4", + "profile": "https://github.com/alejandro-ariza", + "contributions": [ + "ideas" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1b937824d..275e1b314 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-33-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-34-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -52,6 +52,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Shan Zhang
Shan Zhang

🐛 JavierHernandezMontes
JavierHernandezMontes

🐛 Joey Ashcroft
Joey Ashcroft

🐛 + Alejandro Ariza
Alejandro Ariza

🤔 From 20450c8ad840910fa09c86a6d6c05e2ffb44f55e Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:00:05 +0200 Subject: [PATCH 107/192] Add @hovinh as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 166c88df7..83aa03b8e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -431,6 +431,15 @@ "contributions": [ "ideas" ] + }, + { + "login": "hovinh", + "name": "Ho Xuan Vinh", + "avatar_url": "https://avatars.githubusercontent.com/u/7614509?v=4", + "profile": "https://github.com/hovinh", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 275e1b314..4cb42f549 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-34-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-35-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -53,6 +53,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d JavierHernandezMontes
JavierHernandezMontes

🐛 Joey Ashcroft
Joey Ashcroft

🐛 Alejandro Ariza
Alejandro Ariza

🤔 + Ho Xuan Vinh
Ho Xuan Vinh

🐛 From 833fd3d96a191a59bb8246303092767d00301cac Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:00:35 +0200 Subject: [PATCH 108/192] Add @ll-portes as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 83aa03b8e..3c92715a2 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -440,6 +440,15 @@ "contributions": [ "bug" ] + }, + { + "login": "ll-portes", + "name": "Leonardo L. Portes", + "avatar_url": "https://avatars.githubusercontent.com/u/20357023?v=4", + "profile": "https://github.com/ll-portes", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4cb42f549..08ef5ca49 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-35-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-36-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -55,6 +55,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Alejandro Ariza
Alejandro Ariza

🤔 Ho Xuan Vinh
Ho Xuan Vinh

🐛 + + Leonardo L. Portes
Leonardo L. Portes

🐛 + From f2fcc22678e3440c493211cd7bc33759a7e16860 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:01:41 +0200 Subject: [PATCH 109/192] Add @architdatar as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3c92715a2..ac5132bc7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -449,6 +449,15 @@ "contributions": [ "bug" ] + }, + { + "login": "architdatar", + "name": "Archit Datar", + "avatar_url": "https://avatars.githubusercontent.com/u/18289402?v=4", + "profile": "https://github.com/architdatar", + "contributions": [ + "ideas" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 08ef5ca49..cb9d46317 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-36-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-37-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -57,6 +57,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Leonardo L. Portes
Leonardo L. Portes

🐛 + Archit Datar
Archit Datar

🤔 From 72bb89e63a9c03998ceb39c29ff63087e3025e67 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:02:38 +0200 Subject: [PATCH 110/192] Add @adeyemi-lawal as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index ac5132bc7..38684d555 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -458,6 +458,15 @@ "contributions": [ "ideas" ] + }, + { + "login": "adeyemi-lawal", + "name": "adeyemi-lawal", + "avatar_url": "https://avatars.githubusercontent.com/u/48803738?v=4", + "profile": "https://github.com/adeyemi-lawal", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index cb9d46317..e73d60eeb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-37-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-38-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -58,6 +58,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Leonardo L. Portes
Leonardo L. Portes

🐛 Archit Datar
Archit Datar

🤔 + adeyemi-lawal
adeyemi-lawal

🐛 From df0e16a2560978fe7d1ad76013cff1e65dcd880a Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:03:09 +0200 Subject: [PATCH 111/192] Add @arezooorooji as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 38684d555..fe2a988db 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -467,6 +467,15 @@ "contributions": [ "bug" ] + }, + { + "login": "arezooorooji", + "name": "arezooorooji", + "avatar_url": "https://avatars.githubusercontent.com/u/115200663?v=4", + "profile": "https://github.com/arezooorooji", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e73d60eeb..1a19c3849 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-38-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-39-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -59,6 +59,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Leonardo L. Portes
Leonardo L. Portes

🐛 Archit Datar
Archit Datar

🤔 adeyemi-lawal
adeyemi-lawal

🐛 + arezooorooji
arezooorooji

🐛 From 9a035521347344c572ec2411b45c0904878cb0a0 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:03:43 +0200 Subject: [PATCH 112/192] Add @franktoffel as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index fe2a988db..34e25e03a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -476,6 +476,15 @@ "contributions": [ "bug" ] + }, + { + "login": "franktoffel", + "name": "Fran Navarro", + "avatar_url": "https://avatars.githubusercontent.com/u/5269170?v=4", + "profile": "http://cacheme.org/", + "contributions": [ + "ideas" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1a19c3849..ec6c3c4e5 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-39-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-40-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -60,6 +60,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Archit Datar
Archit Datar

🤔 adeyemi-lawal
adeyemi-lawal

🐛 arezooorooji
arezooorooji

🐛 + Fran Navarro
Fran Navarro

🤔 From 41cd2eff6396f0c88069b1593071732f0721ebaf Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:06:53 +0200 Subject: [PATCH 113/192] Add @fbarfi as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 34e25e03a..fa61d1703 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -485,6 +485,15 @@ "contributions": [ "ideas" ] + }, + { + "login": "fbarfi", + "name": "fbarfi", + "avatar_url": "https://avatars.githubusercontent.com/u/73955902?v=4", + "profile": "https://github.com/fbarfi", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ec6c3c4e5..77d217d81 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-40-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-41-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -61,6 +61,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d adeyemi-lawal
adeyemi-lawal

🐛 arezooorooji
arezooorooji

🐛 Fran Navarro
Fran Navarro

🤔 + fbarfi
fbarfi

🐛 From 8dc4a78602fcb9f568977b2bf0ffd39549828531 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:07:25 +0200 Subject: [PATCH 114/192] Add @valcarcexyz as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index fa61d1703..51e0e9a80 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -494,6 +494,15 @@ "contributions": [ "bug" ] + }, + { + "login": "valcarcexyz", + "name": "Diego Valcarce", + "avatar_url": "https://avatars.githubusercontent.com/u/47260541?v=4", + "profile": "https://valcarce.xyz/", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 77d217d81..0c4c3407b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-41-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -62,6 +62,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d arezooorooji
arezooorooji

🐛 Fran Navarro
Fran Navarro

🤔 fbarfi
fbarfi

🐛 + Diego Valcarce
Diego Valcarce

🐛 From b365e7ff688feb1ed78cba675dc839a7f893c23a Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:08:01 +0200 Subject: [PATCH 115/192] Add @shappiron as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 51e0e9a80..33647dffc 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -503,6 +503,15 @@ "contributions": [ "bug" ] + }, + { + "login": "shappiron", + "name": "Dmitrii Kriukov", + "avatar_url": "https://avatars.githubusercontent.com/u/50598651?v=4", + "profile": "https://github.com/shappiron", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0c4c3407b..a12f26029 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-43-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -64,6 +64,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d fbarfi
fbarfi

🐛 Diego Valcarce
Diego Valcarce

🐛 + + Dmitrii Kriukov
Dmitrii Kriukov

🐛 + From c5a8f903c1eec5c95ccc15130870e0ee53cfc1dd Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:09:31 +0200 Subject: [PATCH 116/192] Add @NazZakiyeva as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 33647dffc..da2a693d3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -512,6 +512,15 @@ "contributions": [ "bug" ] + }, + { + "login": "NazZakiyeva", + "name": "Nazgul Zakiyeva", + "avatar_url": "https://avatars.githubusercontent.com/u/86769542?v=4", + "profile": "https://github.com/NazZakiyeva", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a12f26029..6dc5b2e8d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-43-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-44-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -66,6 +66,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Dmitrii Kriukov
Dmitrii Kriukov

🐛 + Nazgul Zakiyeva
Nazgul Zakiyeva

🐛 From 3c2e8f5e91d9d5e6880b264b863ff4da640c4853 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:10:13 +0200 Subject: [PATCH 117/192] Update @ego-thales as a contributor --- .all-contributorsrc | 3 ++- CONTRIBUTORS.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index da2a693d3..d4f946771 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -346,7 +346,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/121242234?v=4", "profile": "https://github.com/ego-thales", "contributions": [ - "doc" + "doc", + "bug" ] }, { diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 6dc5b2e8d..cd09dbeec 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -41,7 +41,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Clément Lejeune
Clément Lejeune

💻 💡 ⚠️ Jorge Duque
Jorge Duque

💻 Saumyaranjan Parida
Saumyaranjan Parida

💻 - ego-thales
ego-thales

📖 + ego-thales
ego-thales

📖 🐛 Derek Tucker
Derek Tucker

💬 Quentin Grimonprez
Quentin Grimonprez

💻 Sean Johnsen
Sean Johnsen

📖 From 351a1259be19e7d3d470255ec95adcc4d5e0af08 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:11:49 +0200 Subject: [PATCH 118/192] Add @sparnez as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index d4f946771..9baae55b4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -522,6 +522,15 @@ "contributions": [ "bug" ] + }, + { + "login": "sparnez", + "name": "sparnez", + "avatar_url": "https://avatars.githubusercontent.com/u/43633383?v=4", + "profile": "https://github.com/sparnez", + "contributions": [ + "ideas" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index cd09dbeec..9f52d388e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-44-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-45-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -67,6 +67,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Dmitrii Kriukov
Dmitrii Kriukov

🐛 Nazgul Zakiyeva
Nazgul Zakiyeva

🐛 + sparnez
sparnez

🤔 From 474fd89c425c6cc4a86d2ac2ab22e95c51b178d0 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 8 Aug 2023 12:12:25 +0200 Subject: [PATCH 119/192] Add @daviddavo as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 9baae55b4..c02c095be 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -531,6 +531,15 @@ "contributions": [ "ideas" ] + }, + { + "login": "daviddavo", + "name": "David Davó", + "avatar_url": "https://avatars.githubusercontent.com/u/10263941?v=4", + "profile": "https://ddavo.me/", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9f52d388e..25e6e410b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-45-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-46-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -68,6 +68,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Dmitrii Kriukov
Dmitrii Kriukov

🐛 Nazgul Zakiyeva
Nazgul Zakiyeva

🐛 sparnez
sparnez

🤔 + David Davó
David Davó

🐛 From 2295f15713c45db36befec4a3128310fb9384b71 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Thu, 10 Aug 2023 09:07:26 +0200 Subject: [PATCH 120/192] Fix image link. --- .all-contributorsrc | 2 +- CONTRIBUTORS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index c02c095be..9595b7bd7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -209,7 +209,7 @@ { "login": "", "name": "Sergio Ruiz Lozano", - "avatar_url": "https://github.com/GAA-UAM/scikit-fda/blob/develop/docs/logos/logo_only/logo_only.png", + "avatar_url": "https://github.com/GAA-UAM/scikit-fda/blob/develop/docs/logos/logo_only/logo_only.png?raw=true", "profile": "https://es.linkedin.com/in/sergioruizlozano", "contributions": [ "design" diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 25e6e410b..35f5b244f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -26,7 +26,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d mellamansanchez
mellamansanchez

🐛 💻 📖 💡 🤔 ⚠️ ElenaPetrunina
ElenaPetrunina

💻 📖 💡 🤔 🚇 🔬 ⚠️ Luis Alberto Rodriguez Ramirez
Luis Alberto Rodriguez Ramirez

🤔 - Sergio Ruiz Lozano
Sergio Ruiz Lozano

🎨 + Sergio Ruiz Lozano
Sergio Ruiz Lozano

🎨 dSerna4
dSerna4

💻 📖 💡 🤔 🔬 ⚠️ From 2f43803a9dee928124c0c82dfda9f6630506923b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Fri, 11 Aug 2023 14:07:48 +0200 Subject: [PATCH 121/192] cleaner elimination (only slices) --- skfda/representation/grid.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 20f70b47a..504243873 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -1101,30 +1101,19 @@ def restrict( for ((a, b), (c, d)) in zip(domain_range, self.domain_range) ) - index_list = [] - new_grid_points = [] - # Eliminate points outside the new range. - for dr, grid_points in zip( - domain_range, - self.grid_points, - ): - keep_index = ( - (dr[0] <= grid_points) - & (grid_points <= dr[1]) - ) - - index_list.append(keep_index) - - new_grid_points.append( - grid_points[keep_index], - ) - - data_matrix = self.data_matrix[(slice(None),) + tuple(index_list)] + index_list = [] + slice_list = [] + for (a, b), grid_points in zip(domain_range,self.grid_points): + ia = np.searchsorted(grid_points, a) + ib = np.searchsorted(grid_points, b, 'right') + slice_list.append(slice(ia, ib)) + grid_points = [g[s] for g, s in zip(self.grid_points, slice_list)] + data_matrix = self.data_matrix[slice(None), *slice_list] return self.copy( domain_range=domain_range, - grid_points=new_grid_points, + grid_points=grid_points, data_matrix=data_matrix, ) From 62e574a7d65523a0618fbcb7f0c49670e2e45909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Fri, 11 Aug 2023 14:08:29 +0200 Subject: [PATCH 122/192] implemented with_bounds. Now need test --- skfda/representation/grid.py | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 504243873..bd7ee40fe 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -1082,12 +1082,16 @@ def copy( # noqa: WPS211 def restrict( self: T, domain_range: DomainRangeLike, + *, + with_bounds: bool = True, ) -> T: """ Restrict the functions to a new domain range. Args: domain_range: New domain range. + with_bounds: Whether or not to ensure domain boundaries + appear in `grid_points`. Returns: Restricted function. @@ -1111,6 +1115,49 @@ def restrict( grid_points = [g[s] for g, s in zip(self.grid_points, slice_list)] data_matrix = self.data_matrix[slice(None), *slice_list] + # Ensure that boundaries are in grid_points. + if with_bounds: + # Proceed dim by dim + for dim, (a, b) in enumerate(domain_range): + dim_points = grid_points[dim] + grid_points[dim] = [] + add_low, add_high = False, False + if a < g: + add_low = True + grid_points[dim].append(a) + if b > gb: + add_high = True + grid_points[dim].append(b) + if not (add_low or add_high): + grid_points[dim] = dim_points + continue + # Interpolate on the edge. + eval_points = np.stack( + np.meshgrid(*grid_points, indexing='ij'), axis=-1 + ) # Shape (*map(len, grid_points), self.dim_domain) + eval_flat = eval_points.reshape(-1, self.dim_domain) + edge_values = self(eval_flat).reshape( + len(self), *map(len, grid_points), self.dim_codomain) + # Add to self. Double if statement to only `insert` once. + if add_low: + if add_high: + data_matrix = np.concatenate( + (edge_values.take(0, dim), + data_matrix, + edge_values.take(1, dim)), + axis=dim) + dim_points = np.concatenate(([a], dim_points, [b])) + else: + data_matrix = np.concatenate( + (edge_values, data_matrix), axis=dim) + dim_points = np.concatenate(([a], dim_points)) + else: + if add_high: + data_matrix = np.concatenate( + (data_matrix, edge_values), axis=dim) + dim_points = np.concatenate((dim_points, [b])) + grid_points[dim] = dim_points + return self.copy( domain_range=domain_range, grid_points=grid_points, From f6960a876f6c7f0fce2f6b2ed4dbfa45900a40e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Fri, 11 Aug 2023 14:37:28 +0200 Subject: [PATCH 123/192] added test restrict with bounds --- skfda/tests/test_grid.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/skfda/tests/test_grid.py b/skfda/tests/test_grid.py index 629755108..f5e2fa8db 100644 --- a/skfda/tests/test_grid.py +++ b/skfda/tests/test_grid.py @@ -337,6 +337,18 @@ def test_evaluate_grid_unaligned(self) -> None: np.testing.assert_allclose(res, expected) + def test_restrict(self) -> None: + """ Test FDataGrid.restrict with bounds. """ + # Test 1 sample function R^3 -> R^5. + grid_points = ([0, 1], [0, 1, 2], [0, 1, 2, 3]) + data_matrix = np.ones(1, 2, 3, 4, 5) + fd = FDataGrid(data_matrix, grid_points) + fd_restricted = fd.restrict(((0, 1), (.5, 1.5), (.5, 2))) + res = fd_restricted.grid_points + expected = ([0, 1], [.5, 1, 1.5], [.5, 1, 2]) + for r, e in zip(res, expected): + np.testing.assert_array_equal(r, e) + if __name__ == '__main__': unittest.main() From 5fa91ccfcf78eb3d6db84b2dc247cee1c373994c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Fri, 11 Aug 2023 14:50:00 +0200 Subject: [PATCH 124/192] typo --- skfda/representation/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index bd7ee40fe..2dcbf76e1 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -1108,7 +1108,7 @@ def restrict( # Eliminate points outside the new range. index_list = [] slice_list = [] - for (a, b), grid_points in zip(domain_range,self.grid_points): + for (a, b), grid_points in zip(domain_range, self.grid_points): ia = np.searchsorted(grid_points, a) ib = np.searchsorted(grid_points, b, 'right') slice_list.append(slice(ia, ib)) From 7ca6d86292e1704f357d6c9c0dd6546818039719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Fri, 11 Aug 2023 14:53:29 +0200 Subject: [PATCH 125/192] typo --- skfda/tests/test_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/tests/test_grid.py b/skfda/tests/test_grid.py index f5e2fa8db..a2d309bc1 100644 --- a/skfda/tests/test_grid.py +++ b/skfda/tests/test_grid.py @@ -341,7 +341,7 @@ def test_restrict(self) -> None: """ Test FDataGrid.restrict with bounds. """ # Test 1 sample function R^3 -> R^5. grid_points = ([0, 1], [0, 1, 2], [0, 1, 2, 3]) - data_matrix = np.ones(1, 2, 3, 4, 5) + data_matrix = np.ones((1, 2, 3, 4, 5)) fd = FDataGrid(data_matrix, grid_points) fd_restricted = fd.restrict(((0, 1), (.5, 1.5), (.5, 2))) res = fd_restricted.grid_points From 150a81e3b17a3380b904551b71f6430a2616b40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Fri, 11 Aug 2023 15:35:44 +0200 Subject: [PATCH 126/192] Fixed <3.11 syntax + index mistake. Test is OK! --- skfda/representation/grid.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 2dcbf76e1..266446f8f 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -1113,7 +1113,7 @@ def restrict( ib = np.searchsorted(grid_points, b, 'right') slice_list.append(slice(ia, ib)) grid_points = [g[s] for g, s in zip(self.grid_points, slice_list)] - data_matrix = self.data_matrix[slice(None), *slice_list] + data_matrix = self.data_matrix[(slice(None),) + tuple(slice_list)] # Ensure that boundaries are in grid_points. if with_bounds: @@ -1122,10 +1122,10 @@ def restrict( dim_points = grid_points[dim] grid_points[dim] = [] add_low, add_high = False, False - if a < g: + if a < dim_points[0]: add_low = True grid_points[dim].append(a) - if b > gb: + if b > dim_points[-1]: add_high = True grid_points[dim].append(b) if not (add_low or add_high): @@ -1142,19 +1142,19 @@ def restrict( if add_low: if add_high: data_matrix = np.concatenate( - (edge_values.take(0, dim), + (edge_values.take([0], axis=dim+1), data_matrix, - edge_values.take(1, dim)), - axis=dim) + edge_values.take([1], axis=dim+1)), + axis=dim+1) dim_points = np.concatenate(([a], dim_points, [b])) else: data_matrix = np.concatenate( - (edge_values, data_matrix), axis=dim) + (edge_values, data_matrix), axis=dim+1) dim_points = np.concatenate(([a], dim_points)) else: if add_high: data_matrix = np.concatenate( - (data_matrix, edge_values), axis=dim) + (data_matrix, edge_values), axis=dim+1) dim_points = np.concatenate((dim_points, [b])) grid_points[dim] = dim_points From 96004ea7e2110285d28e02c669ddd888e40358bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Fri, 11 Aug 2023 16:12:15 +0200 Subject: [PATCH 127/192] pep8 readability --- skfda/representation/grid.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 266446f8f..152c941e8 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -1106,11 +1106,10 @@ def restrict( ) # Eliminate points outside the new range. - index_list = [] slice_list = [] - for (a, b), grid_points in zip(domain_range, self.grid_points): - ia = np.searchsorted(grid_points, a) - ib = np.searchsorted(grid_points, b, 'right') + for (a, b), dim_points in zip(domain_range, self.grid_points): + ia = np.searchsorted(dim_points, a) + ib = np.searchsorted(dim_points, b, 'right') slice_list.append(slice(ia, ib)) grid_points = [g[s] for g, s in zip(self.grid_points, slice_list)] data_matrix = self.data_matrix[(slice(None),) + tuple(slice_list)] From 6a066f716f4eded5f149673aff4fb79d315486bc Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 15 Aug 2023 13:48:56 +0200 Subject: [PATCH 128/192] Improve RMH correction with covariance estimated from data. --- .../stats/covariance/_parametric_gaussian.py | 1 + .../recursive_maxima_hunting.py | 101 +++++++----------- skfda/tests/test_recursive_maxima_hunting.py | 66 ++++++++++++ 3 files changed, 105 insertions(+), 63 deletions(-) diff --git a/skfda/exploratory/stats/covariance/_parametric_gaussian.py b/skfda/exploratory/stats/covariance/_parametric_gaussian.py index b11b03e60..85c267622 100644 --- a/skfda/exploratory/stats/covariance/_parametric_gaussian.py +++ b/skfda/exploratory/stats/covariance/_parametric_gaussian.py @@ -51,6 +51,7 @@ def fit( regressor = GaussianProcessRegressor(kernel=cov) regressor.fit(grid_points, data_matrix.T) + self.cov_ = regressor.kernel_ # TODO: Skip cov computation? # TODO: Use a user-public structure to represent the covariance, diff --git a/skfda/preprocessing/dim_reduction/variable_selection/recursive_maxima_hunting.py b/skfda/preprocessing/dim_reduction/variable_selection/recursive_maxima_hunting.py index e2912e0ae..ca7b377b8 100644 --- a/skfda/preprocessing/dim_reduction/variable_selection/recursive_maxima_hunting.py +++ b/skfda/preprocessing/dim_reduction/variable_selection/recursive_maxima_hunting.py @@ -2,7 +2,6 @@ from __future__ import annotations import abc -import copy import math from typing import ( TYPE_CHECKING, @@ -23,9 +22,14 @@ import numpy.ma as ma import scipy.stats import sklearn.utils +from sklearn.base import clone from typing_extensions import Literal import dcor +from skfda.exploratory.stats.covariance import ( + CovarianceEstimator, + EmpiricalCovariance, +) from ...._utils._sklearn_adapter import ( BaseEstimator, @@ -132,7 +136,7 @@ class Correction(BaseEstimator): """ - def begin(self, X: FDataGrid, y: NDArrayFloat) -> None: + def fit(self, X: FDataGrid, y: NDArrayFloat) -> None: """ Initialize the correction for a run. @@ -239,8 +243,6 @@ class GaussianCorrection(ConditionalMeanCorrection): Parameters: mean: Mean function of the Gaussian process. cov: Covariance function of the Gaussian process. - fit_hyperparameters: If ``True`` the hyperparameters of the - covariance function are optimized for the data. """ @@ -249,40 +251,11 @@ def __init__( *, mean: Union[float, Callable[[NDArrayFloat], NDArrayFloat]] = 0, cov: Union[float, CovarianceLike] = 1, - fit_hyperparameters: bool = False, ) -> None: super().__init__() self.mean = mean self.cov = make_kernel(cov) - self.fit_hyperparameters = fit_hyperparameters - - def begin(self, X: FDataGrid, y: NDArrayFloat) -> None: - if self.fit_hyperparameters: - # TODO: Migrate this to scikit-learn - import GPy - - T = X.grid_points[0] - X_copy = np.copy(X.data_matrix[..., 0]) - - y = np.ravel(y) - for class_label in np.unique(y): - trajectories = X_copy[y == class_label, :] - - mean = np.mean(trajectories, axis=0) - X_copy[y == class_label, :] -= mean - - gpy_kernel = getattr(self.cov, "_PicklableKernel__kernel") - - m = GPy.models.GPRegression( - T[:, None], - X_copy.T, - kernel=gpy_kernel, - ) - m.constrain_positive('') - m.optimize() - - self.cov_ = copy.deepcopy(make_kernel(m.kern)) def _evaluate_mean(self, t: NDArrayFloat) -> NDArrayFloat: @@ -501,40 +474,42 @@ class GaussianSampleCorrection(ConditionalMeanCorrection): Correction assuming that the process is Gaussian and using as the kernel the sample covariance. + Parameters: + cov_estimator: Covariance estimator to use. + """ - def begin(self, X: FDataGrid, y: NDArrayFloat) -> None: + def __init__( + self, + cov_estimator: CovarianceEstimator[FDataGrid] | None = None, + ): + self.cov_estimator = cov_estimator + + def fit(self, X: FDataGrid, y: NDArrayFloat) -> None: - X_copy = np.copy(X.data_matrix[..., 0]) + self.cov_estimator_ = ( + EmpiricalCovariance() + if self.cov_estimator is None + else clone(self.cov_estimator) + ) + + X_matrix_copy = np.copy(X.data_matrix[..., 0]) y = np.ravel(y) for class_label in np.unique(y): - trajectories = X_copy[y == class_label, :] + class_index = (y == class_label) + trajectories = X_matrix_copy[class_index] mean = np.mean(trajectories, axis=0) - X_copy[y == class_label, :] -= mean + X_matrix_copy[class_index] -= mean + + X_copy = X.copy(data_matrix=X_matrix_copy) - self.cov_matrix_ = np.cov(X_copy, rowvar=False) - self.t_ = np.ravel(X.grid_points) + self.cov_estimator_.fit(X_copy) self.gaussian_correction_ = GaussianCorrection( - cov=self.cov_fun, + cov=self.cov_estimator_.covariance_, ) - def cov_fun( - self, - t_0: ArrayLike, - t_1: ArrayLike, - ) -> NDArrayFloat: - i = np.searchsorted(self.t_, t_0) - j = np.searchsorted(self.t_, t_1) - - i_r = np.ravel(i) - j_r = np.ravel(j) - - return self.cov_matrix_[ # type: ignore[no-any-return] - np.ix_(i_r, j_r) - ] - def conditioned( self, *, @@ -977,10 +952,10 @@ def fit( # type: ignore[override] # noqa: D102 y = np.asfarray(y) - correction = ( - self.correction - if self.correction - else UniformCorrection() + self.correction_ = ( + UniformCorrection() + if self.correction is None + else clone(self.correction) ) redundancy_condition = ( @@ -1007,7 +982,7 @@ def fit( # type: ignore[override] # noqa: D102 relevances = [] first_pass = True - correction.begin(X, y) + self.correction_.fit(X, y) while True: dependences = _compute_dependence( @@ -1060,12 +1035,12 @@ def fit( # type: ignore[override] # noqa: D102 mask |= influence_mask # Correct the influence of t_max - X = correction( + X = self.correction_( X=X, selected_index=t_max_index, ) - correction = correction.conditioned( + self.correction_ = self.correction_.conditioned( X=X.data_matrix, T=X.grid_points[0], t_0=X.grid_points[0][t_max_index], @@ -1087,7 +1062,7 @@ def transform(self, X: FDataGrid) -> NDArrayFloat: output = X_matrix[(slice(None),) + self.indexes_] - return output.reshape( # type: ignore[no-any-return] + return output.reshape( X.n_samples, -1, ) diff --git a/skfda/tests/test_recursive_maxima_hunting.py b/skfda/tests/test_recursive_maxima_hunting.py index 4ad5ce821..9a435b8f9 100644 --- a/skfda/tests/test_recursive_maxima_hunting.py +++ b/skfda/tests/test_recursive_maxima_hunting.py @@ -2,9 +2,12 @@ import unittest import numpy as np +import pytest import skfda from skfda.datasets import make_gaussian_process +from skfda.exploratory.stats.covariance import ParametricGaussianCovariance +from skfda.misc.covariances import Exponential from skfda.preprocessing.dim_reduction import variable_selection as vs @@ -65,6 +68,69 @@ def mean_1( # noqa: WPS430 points = X.grid_points[0][point_mask] np.testing.assert_allclose(points, [0.25, 0.5, 0.75], rtol=1e-1) + @pytest.mark.filterwarnings( + 'ignore::sklearn.exceptions.ConvergenceWarning' + ) + def test_fit_exponential(self) -> None: + """ + Test the case for which RMH is optimal. + + It tests a case with two homoscedastic Brownian processes where the + difference of means is piecewise linear. + + In this case RMH should return the points where the linear parts + join. + + """ + n_samples = 10000 + n_features = 100 + + def mean_1( # noqa: WPS430 + t: np.typing.NDArray[np.float_], + ) -> np.typing.NDArray[np.float_]: + + return ( # type: ignore[no-any-return] + np.abs(t - 0.25) + - 2 * np.abs(t - 0.5) + + np.abs(t - 0.75) + ) + + X_0 = make_gaussian_process( + n_samples=n_samples // 2, + n_features=n_features, + cov=Exponential(length_scale=2), + random_state=0, + ) + X_1 = make_gaussian_process( + n_samples=n_samples // 2, + n_features=n_features, + mean=mean_1, + cov=Exponential(length_scale=2), + random_state=1, + ) + X = skfda.concatenate((X_0, X_1)) + + y = np.zeros(n_samples) + y[n_samples // 2:] = 1 + + correction = vs.recursive_maxima_hunting.GaussianSampleCorrection( + cov_estimator=ParametricGaussianCovariance( + cov=Exponential(), + ), + ) + stopping_condition = vs.recursive_maxima_hunting.ScoreThresholdStop( + threshold=0.05, + ) + + rmh = vs.RecursiveMaximaHunting( + correction=correction, + stopping_condition=stopping_condition, + ) + rmh.fit(X, y) + point_mask = rmh.get_support() + points = X.grid_points[0][point_mask] + np.testing.assert_allclose(points, [0.25, 0.5, 0.75], rtol=1e-1) + if __name__ == '__main__': unittest.main() From 7c3fa488a119eac10fbd4170c1d82ff93169191a Mon Sep 17 00:00:00 2001 From: ego-thales <121242234+ego-thales@users.noreply.github.com> Date: Thu, 17 Aug 2023 11:24:57 +0200 Subject: [PATCH 129/192] Test restrict: `with_bounds` explicitly set. --- skfda/tests/test_grid.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skfda/tests/test_grid.py b/skfda/tests/test_grid.py index a2d309bc1..eda8542f3 100644 --- a/skfda/tests/test_grid.py +++ b/skfda/tests/test_grid.py @@ -343,7 +343,8 @@ def test_restrict(self) -> None: grid_points = ([0, 1], [0, 1, 2], [0, 1, 2, 3]) data_matrix = np.ones((1, 2, 3, 4, 5)) fd = FDataGrid(data_matrix, grid_points) - fd_restricted = fd.restrict(((0, 1), (.5, 1.5), (.5, 2))) + restricted_domain = ((0, 1), (.5, 1.5), (.5, 2)) + fd_restricted = fd.restrict(restricted_domain, with_bounds=True) res = fd_restricted.grid_points expected = ([0, 1], [.5, 1, 1.5], [.5, 1, 2]) for r, e in zip(res, expected): From 238ef0492c414abc69ad39c6cc9f9aa22f0e1fc8 Mon Sep 17 00:00:00 2001 From: ego-thales <121242234+ego-thales@users.noreply.github.com> Date: Thu, 17 Aug 2023 11:27:48 +0200 Subject: [PATCH 130/192] restrict: `with_bounds` defaults to `False` --- skfda/representation/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 152c941e8..84e1cc36d 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -1083,7 +1083,7 @@ def restrict( self: T, domain_range: DomainRangeLike, *, - with_bounds: bool = True, + with_bounds: bool = False, ) -> T: """ Restrict the functions to a new domain range. From ab396f646cb8ff4d03921a9191091cbd8f32f9fa Mon Sep 17 00:00:00 2001 From: VNMabus Date: Thu, 17 Aug 2023 13:15:56 +0200 Subject: [PATCH 131/192] Fix tests. --- skfda/preprocessing/smoothing/_kernel_smoothers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skfda/preprocessing/smoothing/_kernel_smoothers.py b/skfda/preprocessing/smoothing/_kernel_smoothers.py index 796541985..dc605fc47 100644 --- a/skfda/preprocessing/smoothing/_kernel_smoothers.py +++ b/skfda/preprocessing/smoothing/_kernel_smoothers.py @@ -11,8 +11,9 @@ from ..._utils._utils import _cartesian_product, _to_grid_points from ...misc.hat_matrix import HatMatrix, NadarayaWatsonHatMatrix -from ...misc.metrics import Metric, PairwiseMetric, l2_distance +from ...misc.metrics import PairwiseMetric, l2_distance from ...typing._base import GridPointsLike, Vector +from ...typing._metric import Metric from ...typing._numpy import NDArrayFloat from ._linear import _LinearSmoother From 2a282270661731410a1b8b7502e4b5a29bb837ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Thu, 17 Aug 2023 14:46:30 +0200 Subject: [PATCH 132/192] restrict revision for maintainability --- skfda/representation/grid.py | 44 +++++------------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 84e1cc36d..8ba82e138 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -1116,46 +1116,14 @@ def restrict( # Ensure that boundaries are in grid_points. if with_bounds: - # Proceed dim by dim + # Update `grid_points` for dim, (a, b) in enumerate(domain_range): dim_points = grid_points[dim] - grid_points[dim] = [] - add_low, add_high = False, False - if a < dim_points[0]: - add_low = True - grid_points[dim].append(a) - if b > dim_points[-1]: - add_high = True - grid_points[dim].append(b) - if not (add_low or add_high): - grid_points[dim] = dim_points - continue - # Interpolate on the edge. - eval_points = np.stack( - np.meshgrid(*grid_points, indexing='ij'), axis=-1 - ) # Shape (*map(len, grid_points), self.dim_domain) - eval_flat = eval_points.reshape(-1, self.dim_domain) - edge_values = self(eval_flat).reshape( - len(self), *map(len, grid_points), self.dim_codomain) - # Add to self. Double if statement to only `insert` once. - if add_low: - if add_high: - data_matrix = np.concatenate( - (edge_values.take([0], axis=dim+1), - data_matrix, - edge_values.take([1], axis=dim+1)), - axis=dim+1) - dim_points = np.concatenate(([a], dim_points, [b])) - else: - data_matrix = np.concatenate( - (edge_values, data_matrix), axis=dim+1) - dim_points = np.concatenate(([a], dim_points)) - else: - if add_high: - data_matrix = np.concatenate( - (data_matrix, edge_values), axis=dim+1) - dim_points = np.concatenate((dim_points, [b])) - grid_points[dim] = dim_points + left = [a] * (a < dim_points[0]) + right = [b] * (b > dim_points[-1]) + grid_points[dim] = np.concatenate((left, dim_points, right)) + # Evaluate + data_matrix = self(grid_points, grid=True) return self.copy( domain_range=domain_range, From ae810e806cf9213342ae563a2816f111497cdec9 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Thu, 17 Aug 2023 16:37:40 +0200 Subject: [PATCH 133/192] Fix Mypy errors. --- skfda/preprocessing/smoothing/_kernel_smoothers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skfda/preprocessing/smoothing/_kernel_smoothers.py b/skfda/preprocessing/smoothing/_kernel_smoothers.py index dc605fc47..e9b56f44d 100644 --- a/skfda/preprocessing/smoothing/_kernel_smoothers.py +++ b/skfda/preprocessing/smoothing/_kernel_smoothers.py @@ -12,7 +12,7 @@ from ..._utils._utils import _cartesian_product, _to_grid_points from ...misc.hat_matrix import HatMatrix, NadarayaWatsonHatMatrix from ...misc.metrics import PairwiseMetric, l2_distance -from ...typing._base import GridPointsLike, Vector +from ...typing._base import GridPointsLike from ...typing._metric import Metric from ...typing._numpy import NDArrayFloat from ._linear import _LinearSmoother @@ -116,7 +116,7 @@ def __init__( *, weights: Optional[NDArrayFloat] = None, output_points: Optional[GridPointsLike] = None, - metric: Metric[Vector] = l2_distance, + metric: Metric[NDArrayFloat] = l2_distance, ): self.kernel_estimator = kernel_estimator self.weights = weights From c5ad317ae521700b127eabfa56bb0f15bde1cae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Thu, 17 Aug 2023 16:40:00 +0200 Subject: [PATCH 134/192] pep and mypy --- skfda/representation/grid.py | 4 ++-- skfda/tests/test_grid.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 8ba82e138..5801d3a01 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -1119,8 +1119,8 @@ def restrict( # Update `grid_points` for dim, (a, b) in enumerate(domain_range): dim_points = grid_points[dim] - left = [a] * (a < dim_points[0]) - right = [b] * (b > dim_points[-1]) + left = [a] if a < dim_points[0] else [] + right = [b] if b > dim_points[-1] else [] grid_points[dim] = np.concatenate((left, dim_points, right)) # Evaluate data_matrix = self(grid_points, grid=True) diff --git a/skfda/tests/test_grid.py b/skfda/tests/test_grid.py index eda8542f3..8cc20547b 100644 --- a/skfda/tests/test_grid.py +++ b/skfda/tests/test_grid.py @@ -1,5 +1,6 @@ """Test FDataGrid behaviour.""" import unittest +from typing import Sequence, Tuple import numpy as np import scipy.stats.mstats @@ -338,15 +339,20 @@ def test_evaluate_grid_unaligned(self) -> None: np.testing.assert_allclose(res, expected) def test_restrict(self) -> None: - """ Test FDataGrid.restrict with bounds. """ + """Test FDataGrid.restrict with bounds.""" # Test 1 sample function R^3 -> R^5. grid_points = ([0, 1], [0, 1, 2], [0, 1, 2, 3]) data_matrix = np.ones((1, 2, 3, 4, 5)) fd = FDataGrid(data_matrix, grid_points) - restricted_domain = ((0, 1), (.5, 1.5), (.5, 2)) + restricted_domain = ((0, 1), (0.5, 1.5), (0.5, 2)) fd_restricted = fd.restrict(restricted_domain, with_bounds=True) res = fd_restricted.grid_points - expected = ([0, 1], [.5, 1, 1.5], [.5, 1, 2]) + expected: Tuple[Sequence[float], ...] = ( + [0, 1], + [0.5, 1, 1.5], + [0.5, 1, 2], + ) + for r, e in zip(res, expected): np.testing.assert_array_equal(r, e) From df19f39cce681b952eff0d8e59b169d820c2c7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Ramos=20Carre=C3=B1o?= Date: Thu, 17 Aug 2023 17:09:29 +0200 Subject: [PATCH 135/192] Update skfda/tests/test_grid.py --- skfda/tests/test_grid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skfda/tests/test_grid.py b/skfda/tests/test_grid.py index 8cc20547b..3c028b21e 100644 --- a/skfda/tests/test_grid.py +++ b/skfda/tests/test_grid.py @@ -352,7 +352,6 @@ def test_restrict(self) -> None: [0.5, 1, 1.5], [0.5, 1, 2], ) - for r, e in zip(res, expected): np.testing.assert_array_equal(r, e) From 02514c41f167def29f2b701cc0964be3913c6b56 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 16:07:53 +0000 Subject: [PATCH 136/192] update CONTRIBUTORS.md --- CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 35f5b244f..218e60a5c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -41,7 +41,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Clément Lejeune
Clément Lejeune

💻 💡 ⚠️ Jorge Duque
Jorge Duque

💻 Saumyaranjan Parida
Saumyaranjan Parida

💻 - ego-thales
ego-thales

📖 🐛 + ego-thales
ego-thales

📖 🐛 💻 🤔 Derek Tucker
Derek Tucker

💬 Quentin Grimonprez
Quentin Grimonprez

💻 Sean Johnsen
Sean Johnsen

📖 From 55b642ea04f9359d9af40762dbae6cbd0e2d859e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 16:07:54 +0000 Subject: [PATCH 137/192] update .all-contributorsrc --- .all-contributorsrc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 9595b7bd7..5ec1f1706 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -347,7 +347,9 @@ "profile": "https://github.com/ego-thales", "contributions": [ "doc", - "bug" + "bug", + "code", + "ideas" ] }, { @@ -543,5 +545,6 @@ } ], "contributorsPerLine": 7, - "linkToUsage": true + "linkToUsage": true, + "commitType": "docs" } From c2e407859ea6f4ea9aa70b1bcc5dbb04c3b5ebfd Mon Sep 17 00:00:00 2001 From: eliegoudout <114467748+eliegoudout@users.noreply.github.com> Date: Thu, 17 Aug 2023 18:49:23 +0200 Subject: [PATCH 138/192] Deleted forgotten bits --- skfda/_utils/_utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/skfda/_utils/_utils.py b/skfda/_utils/_utils.py index 357591baa..9a6382b57 100644 --- a/skfda/_utils/_utils.py +++ b/skfda/_utils/_utils.py @@ -364,10 +364,6 @@ def _evaluate_grid( # noqa: WPS234 object. aligned: If False evaluates each sample in a different grid. - evaluate_method: method to use to evaluate the points - n_samples: number of samples - dim_domain: dimension of the domain - dim_codomain: dimensions of the codomain Returns: Numpy array with dim_domain + 1 dimensions with From f5d59a95ec42f3f06295fbe96cf13d9b6c4dbec9 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Thu, 17 Aug 2023 19:17:04 +0200 Subject: [PATCH 139/192] Fix style issues. --- skfda/exploratory/stats/_stats.py | 8 ++++---- skfda/tests/test_stats.py | 13 ++++--------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 3f52a28cf..ee8557beb 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -4,13 +4,13 @@ from builtins import isinstance from typing import Callable, TypeVar, Union -import numpy as np import functools +import numpy as np from scipy import integrate from scipy.stats import rankdata from ...misc.metrics._lp_distances import l2_distance -from ...representation import FData, FDataGrid, FDataBasis +from ...representation import FData, FDataBasis, FDataGrid from ...typing._metric import Metric from ...typing._numpy import NDArrayFloat from ..depth import Depth, ModifiedBandDepth @@ -139,7 +139,7 @@ def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: if X.dim_domain != 1 or X.dim_codomain != 1: raise NotImplementedError( "Standard deviation only implemented " - "for univariate functions." + "for univariate functions.", ) basis = X.basis @@ -152,7 +152,7 @@ def std_function(t_points: NDArrayFloat) -> NDArrayFloat: ) basis_evaluation = basis(t_points).reshape((-1, 1)) return np.sqrt( - basis_evaluation.T @ coeff_matrix @ basis_evaluation + basis_evaluation.T @ coeff_matrix @ basis_evaluation, ).reshape((1, -1, 1)) return function_to_fdatabasis(f=std_function, new_basis=X.basis) diff --git a/skfda/tests/test_stats.py b/skfda/tests/test_stats.py index fb10ad025..15b11b467 100644 --- a/skfda/tests/test_stats.py +++ b/skfda/tests/test_stats.py @@ -9,7 +9,9 @@ make_gaussian_process, ) from skfda.exploratory.stats import ( - geometric_median, modified_epigraph_index, std + geometric_median, + modified_epigraph_index, + std, ) from skfda.misc.covariances import Gaussian from skfda.representation.basis import FourierBasis @@ -160,8 +162,7 @@ class TestStd(unittest.TestCase): def _test_std_gaussian_fourier(self, n_basis: int) -> None: """ - Test standard deviation using - a gaussian processes and a Fourier basis. + Test standard deviation using a gaussian processes and a Fourier basis. """ start = 0 stop = 1 @@ -183,11 +184,6 @@ def _test_std_gaussian_fourier(self, n_basis: int) -> None: grid = np.linspace(start, stop, n_features) almost_std_fd = std(fd.to_grid(grid)).to_basis(fourier_basis) - """ - when measuring the closeness between std(fd) and the "expected" value, - we are a bit more lenient in the extremes of the domain; as the way of - projecting the std to the basis is different in each case. - """ inner_grid_limit = n_features // 10 inner_grid = grid[inner_grid_limit:-inner_grid_limit] np.testing.assert_allclose( @@ -205,5 +201,4 @@ def _test_std_gaussian_fourier(self, n_basis: int) -> None: def test_std(self) -> None: """Test standard deviation.""" - self._test_std_gaussian_fourier(61) From 3e16d9055dfb4a33c2fd2207ce1d729cf1b544d8 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Thu, 17 Aug 2023 19:20:19 +0200 Subject: [PATCH 140/192] Fix style issues. --- skfda/tests/test_stats.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/skfda/tests/test_stats.py b/skfda/tests/test_stats.py index 15b11b467..ebad595c5 100644 --- a/skfda/tests/test_stats.py +++ b/skfda/tests/test_stats.py @@ -161,9 +161,7 @@ class TestStd(unittest.TestCase): """Test the standard deviation of fuctional data objects.""" def _test_std_gaussian_fourier(self, n_basis: int) -> None: - """ - Test standard deviation using a gaussian processes and a Fourier basis. - """ + """Test standard deviation: gaussian processes and a Fourier basis.""" start = 0 stop = 1 n_features = 1000 From 20c2ffd3352cc2118004b4a09d7b83af182e6e5e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 07:52:49 +0000 Subject: [PATCH 141/192] update CONTRIBUTORS.md --- CONTRIBUTORS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 218e60a5c..8d2a1b271 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-46-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-47-orange.svg?style=flat-square)](#contributors-) ## Contributors ✨ @@ -69,6 +69,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Nazgul Zakiyeva
Nazgul Zakiyeva

🐛 sparnez
sparnez

🤔 David Davó
David Davó

🐛 + eliegoudout
eliegoudout

📖 From 906c9c0f6d362dc2fdf9c1c3e38f2424e6afde9c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 07:52:50 +0000 Subject: [PATCH 142/192] update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 5ec1f1706..abd79de36 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -542,6 +542,15 @@ "contributions": [ "bug" ] + }, + { + "login": "eliegoudout", + "name": "eliegoudout", + "avatar_url": "https://avatars.githubusercontent.com/u/114467748?v=4", + "profile": "https://github.com/eliegoudout", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, From 5f3631be42f009613b1649411e9a9bd8b12a7eb9 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Fri, 25 Aug 2023 16:22:39 +0200 Subject: [PATCH 143/192] Fix some issues outlined in the pull request: * function_to_fdatabasis to _utils * docstring in each std function * use pytest instead of unittest --- skfda/_utils/__init__.py | 1 + skfda/_utils/_utils.py | 36 +++++++++++++- skfda/exploratory/stats/_stats.py | 4 +- skfda/misc/_math.py | 30 ----------- skfda/tests/test_stats.py | 49 ------------------ skfda/tests/test_stats_std.py | 83 +++++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+), 82 deletions(-) create mode 100644 skfda/tests/test_stats_std.py diff --git a/skfda/_utils/__init__.py b/skfda/_utils/__init__.py index 387f22b58..838328a6c 100644 --- a/skfda/_utils/__init__.py +++ b/skfda/_utils/__init__.py @@ -20,6 +20,7 @@ "_same_domain", "_to_grid", "_to_grid_points", + "function_to_fdatabasis", "nquad_vec", ], '_warping': [ diff --git a/skfda/_utils/_utils.py b/skfda/_utils/_utils.py index 357591baa..653af9a34 100644 --- a/skfda/_utils/_utils.py +++ b/skfda/_utils/_utils.py @@ -4,7 +4,6 @@ import functools import numbers -from functools import singledispatch from typing import ( TYPE_CHECKING, Any, @@ -36,7 +35,7 @@ ArrayDTypeT = TypeVar("ArrayDTypeT", bound="np.generic") if TYPE_CHECKING: - from ..representation import FData, FDataGrid + from ..representation import FData, FDataBasis, FDataGrid from ..representation.basis import Basis from ..representation.extrapolation import ExtrapolationLike @@ -609,3 +608,36 @@ def _classifier_get_classes( f'one; got {classes.size} class', ) return classes, y_ind + + +def function_to_fdatabasis( + f: Callable[[NDArrayFloat], NDArrayFloat], + new_basis: Basis, +) -> FDataBasis: + """Express a math function as a FDataBasis with a given basis. + + Args: + f: math function. + new_basis: the basis of the output. + + Returns: + FDataBasis: FDataBasis with calculated coefficients and the new + basis. + """ + from .. import FDataBasis + from ..misc._math import inner_product_matrix + + if isinstance(f, FDataBasis) and f.basis == new_basis: + return f + + inner_prod = inner_product_matrix( + new_basis, + f, + _domain_range=new_basis.domain_range, + ) + + gram_matrix = new_basis.gram_matrix() + + coefs = np.linalg.solve(gram_matrix, inner_prod) + + return FDataBasis(new_basis, coefs.T) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index ee8557beb..c084b9354 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -126,6 +126,7 @@ def std(X: F, ddof: int = 1) -> F: @std.register(FDataGrid) def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: + """Standard deviation of a FDataGrid.""" return X.copy( data_matrix=np.std(X.data_matrix, axis=0, ddof=ddof)[np.newaxis, ...], sample_names=("standard deviation",), @@ -134,7 +135,8 @@ def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: @std.register(FDataBasis) def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: - from ...misc._math import function_to_fdatabasis + """Standard deviation of a FDataBasis.""" + from ..._utils import function_to_fdatabasis if X.dim_domain != 1 or X.dim_codomain != 1: raise NotImplementedError( diff --git a/skfda/misc/_math.py b/skfda/misc/_math.py index 595c9361d..4e02de2f8 100644 --- a/skfda/misc/_math.py +++ b/skfda/misc/_math.py @@ -677,33 +677,3 @@ def cosine_similarity_matrix( return _clip_cosine( inner_matrix / norm1[:, np.newaxis] / norm2[np.newaxis, :], ) - - -def function_to_fdatabasis( - f: Callable[[NDArrayFloat], NDArrayFloat], - new_basis: Basis, -) -> FDataBasis: - """Express a math function as a FDataBasis with a given basis. - - Args: - f: math function. - new_basis: the basis of the output. - - Returns: - FDataBasis: FDataBasis with calculated coefficients and the new - basis. - """ - if isinstance(f, FDataBasis) and f.basis == new_basis: - return f - - inner_prod = inner_product_matrix( - new_basis, - f, - _domain_range=new_basis.domain_range, - ) - - gram_matrix = new_basis.gram_matrix() - - coefs = np.linalg.solve(gram_matrix, inner_prod) - - return FDataBasis(new_basis, coefs.T) diff --git a/skfda/tests/test_stats.py b/skfda/tests/test_stats.py index ebad595c5..79f96ea8e 100644 --- a/skfda/tests/test_stats.py +++ b/skfda/tests/test_stats.py @@ -6,15 +6,11 @@ fetch_phoneme, fetch_tecator, fetch_weather, - make_gaussian_process, ) from skfda.exploratory.stats import ( geometric_median, modified_epigraph_index, - std, ) -from skfda.misc.covariances import Gaussian -from skfda.representation.basis import FourierBasis class TestGeometricMedian(unittest.TestCase): @@ -155,48 +151,3 @@ def test_mei(self) -> None: ]), rtol=1e-5, ) - - -class TestStd(unittest.TestCase): - """Test the standard deviation of fuctional data objects.""" - - def _test_std_gaussian_fourier(self, n_basis: int) -> None: - """Test standard deviation: gaussian processes and a Fourier basis.""" - start = 0 - stop = 1 - n_features = 1000 - - gaussian_process = make_gaussian_process( - start=start, - stop=stop, - n_samples=100, - n_features=n_features, - mean=0.0, - cov=Gaussian(variance=1, length_scale=0.1), - random_state=0, - ) - fourier_basis = FourierBasis(n_basis=n_basis, domain_range=(0, 1)) - fd = gaussian_process.to_basis(fourier_basis) - - std_fd = std(fd) - grid = np.linspace(start, stop, n_features) - almost_std_fd = std(fd.to_grid(grid)).to_basis(fourier_basis) - - inner_grid_limit = n_features // 10 - inner_grid = grid[inner_grid_limit:-inner_grid_limit] - np.testing.assert_allclose( - std_fd(inner_grid), - almost_std_fd(inner_grid), - rtol=1e-3, - ) - - outer_grid = grid[:inner_grid_limit] + grid[-inner_grid_limit:] - np.testing.assert_allclose( - std_fd(outer_grid), - almost_std_fd(outer_grid), - rtol=1e-2, - ) - - def test_std(self) -> None: - """Test standard deviation.""" - self._test_std_gaussian_fourier(61) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py new file mode 100644 index 000000000..5b487f730 --- /dev/null +++ b/skfda/tests/test_stats_std.py @@ -0,0 +1,83 @@ +"""Test stats functions.""" + +from __future__ import annotations + +from typing import Any + +import numpy as np +import pytest + +from skfda import FDataGrid +from skfda.datasets import make_gaussian_process +from skfda.exploratory.stats import std +from skfda.misc.covariances import Gaussian +from skfda.representation.basis import FourierBasis + + +@pytest.fixture(params=[61, 71]) +def n_basis(request: Any) -> int: + """Fixture for n_basis to test.""" + return request.param + + +@pytest.fixture +def start() -> int: + """Fixture for the infimum of the domain.""" + return 0 + + +@pytest.fixture +def stop() -> int: + """Fixture for the supremum of the domain.""" + return 1 + + +@pytest.fixture +def n_features() -> int: + """Fixture for the number of features.""" + return 1000 + + +@pytest.fixture +def gaussian_process(start: int, stop: int, n_features: int) -> FDataGrid: + """Fixture for a Gaussian process.""" + return make_gaussian_process( + start=start, + stop=stop, + n_samples=100, + n_features=n_features, + mean=0.0, + cov=Gaussian(variance=1, length_scale=0.1), + random_state=0, + ) + + +def test_std_gaussian_fourier( + start: int, + stop: int, + n_features: int, + n_basis: int, + gaussian_process: FDataGrid, +) -> None: + """Test standard deviation: Gaussian processes and a Fourier basis.""" + fourier_basis = FourierBasis(n_basis=n_basis, domain_range=(0, 1)) + fd = gaussian_process.to_basis(fourier_basis) + + std_fd = std(fd) + grid = np.linspace(start, stop, n_features) + almost_std_fd = std(fd.to_grid(grid)).to_basis(fourier_basis) + + inner_grid_limit = n_features // 10 + inner_grid = grid[inner_grid_limit:-inner_grid_limit] + np.testing.assert_allclose( + std_fd(inner_grid), + almost_std_fd(inner_grid), + rtol=1e-3, + ) + + outer_grid = grid[:inner_grid_limit] + grid[-inner_grid_limit:] + np.testing.assert_allclose( + std_fd(outer_grid), + almost_std_fd(outer_grid), + rtol=1e-2, + ) From 4c2757e19d4c868fce1233fe43c0af30ee6f4741 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Fri, 25 Aug 2023 17:24:58 +0200 Subject: [PATCH 144/192] Test for std_fdatagrid. --- skfda/tests/test_stats_std.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index 5b487f730..a2162f6cb 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -8,6 +8,7 @@ import pytest from skfda import FDataGrid +from skfda.typing._numpy import NDArrayFloat from skfda.datasets import make_gaussian_process from skfda.exploratory.stats import std from skfda.misc.covariances import Gaussian @@ -81,3 +82,29 @@ def test_std_gaussian_fourier( almost_std_fd(outer_grid), rtol=1e-2, ) + + +@pytest.mark.parametrize("fdatagrid, expected_std_data_matrix", [ + ( + FDataGrid( + data_matrix=[ + [[0, 1, 2, 3, 4, 5], [0, -1, -2, -3, -4, -5]], + [[2, 3, 4, 5, 6, 7], [-2, -3, -4, -5, -6, -7]], + ], + grid_points=[ + [-2, -1], + [0, 1, 2, 3, 4, 5] + ], + ), + np.full((1, 2, 6, 1), np.sqrt(2)) + ), +]) +def test_std_fdatagrid( + fdatagrid: FDataGrid, + expected_std_data_matrix: NDArrayFloat, +) -> None: + """Test some FDataGrids' stds.""" + np.testing.assert_allclose( + std(fdatagrid).data_matrix, + expected_std_data_matrix + ) From 23b2d4e731bd5f23aacfd09466a11dce70361a24 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Fri, 25 Aug 2023 17:27:44 +0200 Subject: [PATCH 145/192] Fix style. --- skfda/tests/test_stats_std.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index a2162f6cb..d350e97c6 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -106,5 +106,5 @@ def test_std_fdatagrid( """Test some FDataGrids' stds.""" np.testing.assert_allclose( std(fdatagrid).data_matrix, - expected_std_data_matrix + expected_std_data_matrix, ) From 7137aba096d187870fbba5b3b8b8e23e252dcd9d Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sat, 26 Aug 2023 07:57:05 +0200 Subject: [PATCH 146/192] Test for std_fdatagrid. --- skfda/tests/test_stats_std.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index d350e97c6..a6908669c 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -96,7 +96,26 @@ def test_std_gaussian_fourier( [0, 1, 2, 3, 4, 5] ], ), - np.full((1, 2, 6, 1), np.sqrt(2)) + np.full((1, 2, 6, 1), np.sqrt(2)), + ), + ( + FDataGrid( + data_matrix=[ + [ + [[10, 11], [10, 12], [11, 14]], + [[15, 16], [12, 15], [20, 13]], + ], + [ + [[11, 12], [11, 13], [12, 13]], + [[14, 15], [11, 16], [21, 12]], + ], + ], + grid_points=[ + [0, 1], + [0, 1, 2] + ], + ), + np.full((1, 2, 3, 2), np.sqrt(1/2)), ), ]) def test_std_fdatagrid( From 71f7071b0a928de9083640f95c5f685d9bb71b0e Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sat, 26 Aug 2023 09:00:05 +0200 Subject: [PATCH 147/192] More than 1 evaluation at a time in std_fdatabasis --- skfda/exploratory/stats/_stats.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index c084b9354..c119e2ccc 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -145,17 +145,14 @@ def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: ) basis = X.basis - coeff_matrix = np.cov(X.coefficients, rowvar=False, ddof=ddof) + coeff_cov_matrix = np.cov(X.coefficients, rowvar=False, ddof=ddof) \ + .reshape((basis.n_basis, basis.n_basis)) def std_function(t_points: NDArrayFloat) -> NDArrayFloat: - assert len(t_points) == 1, ( - "Standard deviation function only implemented for " - "one-point-at-a-time evaluations." - ) - basis_evaluation = basis(t_points).reshape((-1, 1)) + basis_evaluation = basis(t_points).reshape((basis.n_basis, -1)) return np.sqrt( - basis_evaluation.T @ coeff_matrix @ basis_evaluation, - ).reshape((1, -1, 1)) + basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation, + ).reshape((1, -1, X.dim_codomain)) return function_to_fdatabasis(f=std_function, new_basis=X.basis) From 614aac5726fb37bebce066e41896d51b6bc3bb8b Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 27 Aug 2023 21:17:41 +0200 Subject: [PATCH 148/192] Fix test_stats_std style issues. --- skfda/tests/test_stats_std.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index a6908669c..ef1017c42 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -93,7 +93,7 @@ def test_std_gaussian_fourier( ], grid_points=[ [-2, -1], - [0, 1, 2, 3, 4, 5] + [0, 1, 2, 3, 4, 5], ], ), np.full((1, 2, 6, 1), np.sqrt(2)), @@ -112,17 +112,17 @@ def test_std_gaussian_fourier( ], grid_points=[ [0, 1], - [0, 1, 2] + [0, 1, 2], ], ), - np.full((1, 2, 3, 2), np.sqrt(1/2)), + np.full((1, 2, 3, 2), np.sqrt(1 / 2)), ), ]) def test_std_fdatagrid( fdatagrid: FDataGrid, expected_std_data_matrix: NDArrayFloat, ) -> None: - """Test some FDataGrids' stds.""" + """Test some std_fdatagrid cases.""" np.testing.assert_allclose( std(fdatagrid).data_matrix, expected_std_data_matrix, From 7a8ece04f7bcc929eba3445d129abe4d7a54daf4 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Mon, 28 Aug 2023 18:11:38 +0200 Subject: [PATCH 149/192] Exact std function improved to accept more than one input and to return multivalued (vector) outputs. Fix some pep8 issues. --- skfda/exploratory/stats/_stats.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index c119e2ccc..9228c74d0 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -126,7 +126,7 @@ def std(X: F, ddof: int = 1) -> F: @std.register(FDataGrid) def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: - """Standard deviation of a FDataGrid.""" + """Compute the standard deviation of a FDataGrid.""" return X.copy( data_matrix=np.std(X.data_matrix, axis=0, ddof=ddof)[np.newaxis, ...], sample_names=("standard deviation",), @@ -135,23 +135,23 @@ def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: @std.register(FDataBasis) def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: - """Standard deviation of a FDataBasis.""" + """Compute the standard deviation of a FDataBasis.""" from ..._utils import function_to_fdatabasis if X.dim_domain != 1 or X.dim_codomain != 1: raise NotImplementedError( - "Standard deviation only implemented " - "for univariate functions.", + "Standard deviation only implemented for univariate functions.", ) basis = X.basis - coeff_cov_matrix = np.cov(X.coefficients, rowvar=False, ddof=ddof) \ - .reshape((basis.n_basis, basis.n_basis)) + coeff_cov_matrix = np.cov( + X.coefficients, rowvar=False, ddof=ddof, + ).reshape((basis.n_basis, basis.n_basis)) def std_function(t_points: NDArrayFloat) -> NDArrayFloat: basis_evaluation = basis(t_points).reshape((basis.n_basis, -1)) return np.sqrt( - basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation, + np.diag(basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation), ).reshape((1, -1, X.dim_codomain)) return function_to_fdatabasis(f=std_function, new_basis=X.basis) From eb80f31013dd15a7de2993374853fb681f1ba8c8 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Mon, 4 Sep 2023 19:19:01 +0200 Subject: [PATCH 150/192] Undo test_stats changes --- skfda/tests/test_stats.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/skfda/tests/test_stats.py b/skfda/tests/test_stats.py index 79f96ea8e..22b74a437 100644 --- a/skfda/tests/test_stats.py +++ b/skfda/tests/test_stats.py @@ -2,15 +2,8 @@ import numpy as np -from skfda.datasets import ( - fetch_phoneme, - fetch_tecator, - fetch_weather, -) -from skfda.exploratory.stats import ( - geometric_median, - modified_epigraph_index, -) +from skfda.datasets import fetch_phoneme, fetch_tecator, fetch_weather +from skfda.exploratory.stats import geometric_median, modified_epigraph_index class TestGeometricMedian(unittest.TestCase): From 32233c98bc5f0ca949b46b7e4f72e04fed84ff78 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Mon, 4 Sep 2023 19:39:50 +0200 Subject: [PATCH 151/192] Import order --- skfda/tests/test_stats_std.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index ef1017c42..86d818a9d 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -8,11 +8,11 @@ import pytest from skfda import FDataGrid -from skfda.typing._numpy import NDArrayFloat from skfda.datasets import make_gaussian_process from skfda.exploratory.stats import std from skfda.misc.covariances import Gaussian from skfda.representation.basis import FourierBasis +from skfda.typing._numpy import NDArrayFloat @pytest.fixture(params=[61, 71]) From 026d4883999f524ea9b339df00888cec19642f0a Mon Sep 17 00:00:00 2001 From: pcuestas Date: Thu, 14 Sep 2023 13:52:39 +0200 Subject: [PATCH 152/192] std_fdatabasis with dim>1 (domain or/and codomain) + tests --- skfda/exploratory/stats/_stats.py | 5 -- skfda/tests/test_stats_std.py | 82 ++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 9228c74d0..4cf784ef3 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -138,11 +138,6 @@ def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: """Compute the standard deviation of a FDataBasis.""" from ..._utils import function_to_fdatabasis - if X.dim_domain != 1 or X.dim_codomain != 1: - raise NotImplementedError( - "Standard deviation only implemented for univariate functions.", - ) - basis = X.basis coeff_cov_matrix = np.cov( X.coefficients, rowvar=False, ddof=ddof, diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index 86d818a9d..a37196a31 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -7,11 +7,16 @@ import numpy as np import pytest -from skfda import FDataGrid +from skfda import FDataBasis, FDataGrid from skfda.datasets import make_gaussian_process from skfda.exploratory.stats import std from skfda.misc.covariances import Gaussian -from skfda.representation.basis import FourierBasis +from skfda.representation.basis import ( + FourierBasis, + MonomialBasis, + TensorBasis, + VectorValuedBasis, +) from skfda.typing._numpy import NDArrayFloat @@ -127,3 +132,76 @@ def test_std_fdatagrid( std(fdatagrid).data_matrix, expected_std_data_matrix, ) + + +@pytest.mark.parametrize("fdatabasis, expected_std_coefficients", [ + ( + FDataBasis( + basis=VectorValuedBasis([ + MonomialBasis(domain_range=(0, 1), n_basis=3), + MonomialBasis(domain_range=(0, 1), n_basis=3), + ]), + coefficients=[ + [0, 0, 0, 0, 0, 0], + [1, 0, 0, 1, 0, 0], + ], + ), + np.array([[np.sqrt(1 / 2), 0, 0, np.sqrt(1 / 2), 0, 0]]), + ), + ( + FDataBasis( + basis=VectorValuedBasis([ + FourierBasis(domain_range=(0, 1), n_basis=5), + MonomialBasis(domain_range=(0, 1), n_basis=4), + ]), + coefficients=[ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 1, 0, 0, 0], + ], + ), + np.array([[np.sqrt(1 / 2), 0, 0, 0, 0, np.sqrt(1 / 2), 0, 0, 0]]), + ), + ( + FDataBasis( + basis=TensorBasis([ + MonomialBasis(domain_range=(0, 1), n_basis=4), + MonomialBasis(domain_range=(0, 1), n_basis=4), + ]), + coefficients=[ + np.zeros(16), + np.pad([1], (0, 15)), + ], + ), + [np.pad([np.sqrt(1 / 2)], (0, 15))], + ), + ( + FDataBasis( + basis=VectorValuedBasis([ + TensorBasis([ + MonomialBasis(domain_range=(0, 1), n_basis=2), + MonomialBasis(domain_range=(0, 1), n_basis=2), + ]), + TensorBasis([ + MonomialBasis(domain_range=(0, 1), n_basis=2), + MonomialBasis(domain_range=(0, 1), n_basis=2), + ]), + ]), + coefficients=[ + [0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 1, 0, 0, 0], + ], + ), + np.array([[np.sqrt(1 / 2), 0, 0, 0] * 2]), + ), +]) +def test_std_fdatabasis( + fdatabasis: FDataBasis, + expected_std_coefficients: NDArrayFloat, +) -> None: + """Test some std_fdatabasis cases.""" + np.testing.assert_allclose( + std(fdatabasis).coefficients, + expected_std_coefficients, + rtol=1e-7, + atol=1e-7, + ) From 88c7fd443d9ab2542fb988ef6845174781d85a16 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 17 Sep 2023 12:44:44 +0200 Subject: [PATCH 153/192] Do not return input in function_to_fdatabasis, but a copy. No sample names for std_fdatagrid. --- skfda/_utils/_utils.py | 2 +- skfda/exploratory/stats/_stats.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skfda/_utils/_utils.py b/skfda/_utils/_utils.py index fd3dcfeb7..d35617130 100644 --- a/skfda/_utils/_utils.py +++ b/skfda/_utils/_utils.py @@ -624,7 +624,7 @@ def function_to_fdatabasis( from ..misc._math import inner_product_matrix if isinstance(f, FDataBasis) and f.basis == new_basis: - return f + return f.copy() inner_prod = inner_product_matrix( new_basis, diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 4cf784ef3..c2267190d 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -129,7 +129,7 @@ def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: """Compute the standard deviation of a FDataGrid.""" return X.copy( data_matrix=np.std(X.data_matrix, axis=0, ddof=ddof)[np.newaxis, ...], - sample_names=("standard deviation",), + sample_names=(None,), ) From 0457e39e02119f1bfa675e422d68839aff2fda1d Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 17 Sep 2023 12:45:32 +0200 Subject: [PATCH 154/192] Fix test_stats_std readability. Remove confusing Gaussian processes test. --- skfda/tests/test_stats_std.py | 316 +++++++++++++++++----------------- 1 file changed, 159 insertions(+), 157 deletions(-) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index a37196a31..250d78650 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -8,200 +8,202 @@ import pytest from skfda import FDataBasis, FDataGrid -from skfda.datasets import make_gaussian_process from skfda.exploratory.stats import std -from skfda.misc.covariances import Gaussian from skfda.representation.basis import ( + Basis, + BSplineBasis, FourierBasis, MonomialBasis, TensorBasis, VectorValuedBasis, ) -from skfda.typing._numpy import NDArrayFloat -@pytest.fixture(params=[61, 71]) -def n_basis(request: Any) -> int: - """Fixture for n_basis to test.""" - return request.param +# Fixtures for test_std_fdatabasis_vector_valued_basis - -@pytest.fixture -def start() -> int: - """Fixture for the infimum of the domain.""" - return 0 +@pytest.fixture(params=[3, 5]) +def vv_n_basis1(request: Any) -> int: + """n_basis for 1st coordinate of vector valued basis.""" + return request.param # type: ignore -@pytest.fixture -def stop() -> int: - """Fixture for the supremum of the domain.""" - return 1 +@pytest.fixture(params=[3]) +def vv_n_basis2(request: Any) -> int: + """n_basis for 2nd coordinate of vector valued basis.""" + return request.param # type: ignore @pytest.fixture -def n_features() -> int: - """Fixture for the number of features.""" - return 1000 +def vv_basis1(vv_n_basis1: int) -> Basis: + """1-dimensional basis to test for vector valued basis.""" + # First element of the basis is assumed to be the 1 function + return BSplineBasis( + n_basis=vv_n_basis1, + domain_range=(0, 1), + order=vv_n_basis1 - 1, + ) -@pytest.fixture -def gaussian_process(start: int, stop: int, n_features: int) -> FDataGrid: - """Fixture for a Gaussian process.""" - return make_gaussian_process( - start=start, - stop=stop, - n_samples=100, - n_features=n_features, - mean=0.0, - cov=Gaussian(variance=1, length_scale=0.1), - random_state=0, +@pytest.fixture(params=[FourierBasis, MonomialBasis]) +def vv_basis2(request: Any, vv_n_basis2: int) -> Basis: + """1-dimensional basis to test for vector valued basis.""" + # First element of the basis is assumed to be the 1 function + return request.param( # type: ignore + domain_range=(0, 1), n_basis=vv_n_basis2, ) -def test_std_gaussian_fourier( - start: int, - stop: int, - n_features: int, - n_basis: int, - gaussian_process: FDataGrid, -) -> None: - """Test standard deviation: Gaussian processes and a Fourier basis.""" - fourier_basis = FourierBasis(n_basis=n_basis, domain_range=(0, 1)) - fd = gaussian_process.to_basis(fourier_basis) +# Fixtures for test_std_fdatabasis_tensor_basis - std_fd = std(fd) - grid = np.linspace(start, stop, n_features) - almost_std_fd = std(fd.to_grid(grid)).to_basis(fourier_basis) +@pytest.fixture(params=[3]) +def t_n_basis1(request: Any) -> int: + """n_basis for 1st input argument of tensor basis.""" + return request.param # type: ignore - inner_grid_limit = n_features // 10 - inner_grid = grid[inner_grid_limit:-inner_grid_limit] - np.testing.assert_allclose( - std_fd(inner_grid), - almost_std_fd(inner_grid), - rtol=1e-3, + +@pytest.fixture(params=[5]) +def t_n_basis2(request: Any) -> int: + """n_basis for 2nd input argument of tensor basis.""" + return request.param # type: ignore + + +@pytest.fixture(params=[FourierBasis]) +def t_basis1(request: Any, t_n_basis2: int) -> Basis: + """1-dimensional basis to test for tensor basis.""" + # First element of the basis is assumed to be the 1 function + return request.param( # type: ignore + domain_range=(0, 1), n_basis=t_n_basis2, ) - outer_grid = grid[:inner_grid_limit] + grid[-inner_grid_limit:] + +@pytest.fixture(params=[MonomialBasis]) +def t_basis2(request: Any, t_n_basis2: int) -> Basis: + """1-dimensional basis to test for tensor basis.""" + # First element of the basis is assumed to be the 1 function + return request.param( # type: ignore + domain_range=(0, 1), n_basis=t_n_basis2, + ) + + +# Tests + +def test_std_fdatagrid_1d_to_2d() -> None: + """Test std_fdatagrid with R to R^2 functions.""" + fd = FDataGrid( + data_matrix=[ + [[0, 1, 2, 3, 4, 5], [0, -1, -2, -3, -4, -5]], + [[2, 3, 4, 5, 6, 7], [-2, -3, -4, -5, -6, -7]], + ], + grid_points=[ + [-2, -1], + [0, 1, 2, 3, 4, 5], + ], + ) + expected_std_data_matrix = np.full((1, 2, 6, 1), np.sqrt(2)) np.testing.assert_allclose( - std_fd(outer_grid), - almost_std_fd(outer_grid), - rtol=1e-2, + std(fd).data_matrix, + expected_std_data_matrix, ) -@pytest.mark.parametrize("fdatagrid, expected_std_data_matrix", [ - ( - FDataGrid( - data_matrix=[ - [[0, 1, 2, 3, 4, 5], [0, -1, -2, -3, -4, -5]], - [[2, 3, 4, 5, 6, 7], [-2, -3, -4, -5, -6, -7]], - ], - grid_points=[ - [-2, -1], - [0, 1, 2, 3, 4, 5], +def test_std_fdatagrid_2d_to_2d() -> None: + """Test std_fdatagrid with R to R^2 functions.""" + fd = FDataGrid( + data_matrix=[ + [ + [[10, 11], [10, 12], [11, 14]], + [[15, 16], [12, 15], [20, 13]], ], - ), - np.full((1, 2, 6, 1), np.sqrt(2)), - ), - ( - FDataGrid( - data_matrix=[ - [ - [[10, 11], [10, 12], [11, 14]], - [[15, 16], [12, 15], [20, 13]], - ], - [ - [[11, 12], [11, 13], [12, 13]], - [[14, 15], [11, 16], [21, 12]], - ], + [ + [[11, 12], [11, 13], [12, 13]], + [[14, 15], [11, 16], [21, 12]], ], - grid_points=[ - [0, 1], - [0, 1, 2], - ], - ), - np.full((1, 2, 3, 2), np.sqrt(1 / 2)), - ), -]) -def test_std_fdatagrid( - fdatagrid: FDataGrid, - expected_std_data_matrix: NDArrayFloat, -) -> None: - """Test some std_fdatagrid cases.""" + ], + grid_points=[ + [0, 1], + [0, 1, 2], + ], + ) + expected_std_data_matrix = np.full((1, 2, 3, 2), np.sqrt(1 / 2)) np.testing.assert_allclose( - std(fdatagrid).data_matrix, + std(fd).data_matrix, expected_std_data_matrix, ) -@pytest.mark.parametrize("fdatabasis, expected_std_coefficients", [ - ( - FDataBasis( - basis=VectorValuedBasis([ - MonomialBasis(domain_range=(0, 1), n_basis=3), - MonomialBasis(domain_range=(0, 1), n_basis=3), - ]), - coefficients=[ - [0, 0, 0, 0, 0, 0], - [1, 0, 0, 1, 0, 0], - ], - ), - np.array([[np.sqrt(1 / 2), 0, 0, np.sqrt(1 / 2), 0, 0]]), - ), - ( - FDataBasis( - basis=VectorValuedBasis([ - FourierBasis(domain_range=(0, 1), n_basis=5), - MonomialBasis(domain_range=(0, 1), n_basis=4), - ]), - coefficients=[ - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 0, 1, 0, 0, 0], - ], - ), - np.array([[np.sqrt(1 / 2), 0, 0, 0, 0, np.sqrt(1 / 2), 0, 0, 0]]), - ), - ( - FDataBasis( - basis=TensorBasis([ - MonomialBasis(domain_range=(0, 1), n_basis=4), - MonomialBasis(domain_range=(0, 1), n_basis=4), - ]), - coefficients=[ - np.zeros(16), - np.pad([1], (0, 15)), - ], - ), - [np.pad([np.sqrt(1 / 2)], (0, 15))], - ), - ( - FDataBasis( - basis=VectorValuedBasis([ - TensorBasis([ - MonomialBasis(domain_range=(0, 1), n_basis=2), - MonomialBasis(domain_range=(0, 1), n_basis=2), - ]), - TensorBasis([ - MonomialBasis(domain_range=(0, 1), n_basis=2), - MonomialBasis(domain_range=(0, 1), n_basis=2), - ]), - ]), - coefficients=[ - [0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 1, 0, 0, 0], - ], - ), - np.array([[np.sqrt(1 / 2), 0, 0, 0] * 2]), - ), -]) -def test_std_fdatabasis( - fdatabasis: FDataBasis, - expected_std_coefficients: NDArrayFloat, +def test_std_fdatabasis_vector_valued_basis( + vv_basis1: Basis, + vv_basis2: Basis, +) -> None: + """Test std_fdatabasis with a vector valued basis.""" + basis = VectorValuedBasis([vv_basis1, vv_basis2]) + + # coefficients of the function===(1, 1) + one_coefficients = np.concatenate(( + np.pad([1], (0, vv_basis1.n_basis - 1)), + np.pad([1], (0, vv_basis2.n_basis - 1)), + )) + + fd = FDataBasis( + basis=basis, + coefficients=[np.zeros(basis.n_basis), one_coefficients], + ) + + np.testing.assert_allclose( + std(fd).coefficients, + np.array([np.sqrt(1 / 2) * one_coefficients]), + rtol=1e-7, + atol=1e-7, + ) + + +def test_std_fdatabasis_tensor_basis( + t_basis1: Basis, + t_basis2: Basis, ) -> None: - """Test some std_fdatabasis cases.""" + """Test std_fdatabasis with a vector valued basis.""" + basis = TensorBasis([t_basis1, t_basis2]) + + # coefficients of the function===1 + one_coefficients = np.pad([1], (0, basis.n_basis - 1)) + + fd = FDataBasis( + basis=basis, + coefficients=[np.zeros(basis.n_basis), one_coefficients], + ) + + np.testing.assert_allclose( + std(fd).coefficients, + np.array([np.sqrt(1 / 2) * one_coefficients]), + rtol=1e-7, + atol=1e-7, + ) + + +def test_std_fdatabasis_2d_to_2d() -> None: + """Test std_fdatabasis with R^2 to R^2 basis.""" + basis = VectorValuedBasis([ + TensorBasis([ + MonomialBasis(domain_range=(0, 1), n_basis=2), + MonomialBasis(domain_range=(0, 1), n_basis=2), + ]), + TensorBasis([ + MonomialBasis(domain_range=(0, 1), n_basis=2), + MonomialBasis(domain_range=(0, 1), n_basis=2), + ]), + ]) + fd = FDataBasis( + basis=basis, + coefficients=[ + [0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 1, 0, 0, 0], + ], + ) + expected_coefficients = np.array([[np.sqrt(1 / 2), 0, 0, 0] * 2]) + np.testing.assert_allclose( - std(fdatabasis).coefficients, - expected_std_coefficients, + std(fd).coefficients, + expected_coefficients, rtol=1e-7, atol=1e-7, ) From ccb8ed495e6d2a8c41461276d7eb24c2c7ee3b2a Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 17 Sep 2023 13:11:14 +0200 Subject: [PATCH 155/192] mypy ignore[no-any-return] on fixtures --- skfda/tests/test_stats_std.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index 250d78650..f4c10b8a1 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -18,19 +18,18 @@ VectorValuedBasis, ) - # Fixtures for test_std_fdatabasis_vector_valued_basis @pytest.fixture(params=[3, 5]) def vv_n_basis1(request: Any) -> int: """n_basis for 1st coordinate of vector valued basis.""" - return request.param # type: ignore + return request.param # type: ignore[no-any-return] @pytest.fixture(params=[3]) def vv_n_basis2(request: Any) -> int: """n_basis for 2nd coordinate of vector valued basis.""" - return request.param # type: ignore + return request.param # type: ignore[no-any-return] @pytest.fixture @@ -48,7 +47,7 @@ def vv_basis1(vv_n_basis1: int) -> Basis: def vv_basis2(request: Any, vv_n_basis2: int) -> Basis: """1-dimensional basis to test for vector valued basis.""" # First element of the basis is assumed to be the 1 function - return request.param( # type: ignore + return request.param( # type: ignore[no-any-return] domain_range=(0, 1), n_basis=vv_n_basis2, ) @@ -58,20 +57,20 @@ def vv_basis2(request: Any, vv_n_basis2: int) -> Basis: @pytest.fixture(params=[3]) def t_n_basis1(request: Any) -> int: """n_basis for 1st input argument of tensor basis.""" - return request.param # type: ignore + return request.param # type: ignore[no-any-return] @pytest.fixture(params=[5]) def t_n_basis2(request: Any) -> int: """n_basis for 2nd input argument of tensor basis.""" - return request.param # type: ignore + return request.param # type: ignore[no-any-return] @pytest.fixture(params=[FourierBasis]) def t_basis1(request: Any, t_n_basis2: int) -> Basis: """1-dimensional basis to test for tensor basis.""" # First element of the basis is assumed to be the 1 function - return request.param( # type: ignore + return request.param( # type: ignore[no-any-return] domain_range=(0, 1), n_basis=t_n_basis2, ) @@ -80,7 +79,7 @@ def t_basis1(request: Any, t_n_basis2: int) -> Basis: def t_basis2(request: Any, t_n_basis2: int) -> Basis: """1-dimensional basis to test for tensor basis.""" # First element of the basis is assumed to be the 1 function - return request.param( # type: ignore + return request.param( # type: ignore[no-any-return] domain_range=(0, 1), n_basis=t_n_basis2, ) From c7f70a18a01d81635926e90590b9368604d604ba Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 17 Sep 2023 13:34:48 +0200 Subject: [PATCH 156/192] Fix typing issues in std_function inside of std_fdatabasis --- skfda/exploratory/stats/_stats.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index c2267190d..4af295b0c 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -145,9 +145,12 @@ def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: def std_function(t_points: NDArrayFloat) -> NDArrayFloat: basis_evaluation = basis(t_points).reshape((basis.n_basis, -1)) - return np.sqrt( - np.diag(basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation), - ).reshape((1, -1, X.dim_codomain)) + return np.reshape( + np.sqrt(np.diag( + basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation, + )), + (1, -1, X.dim_codomain), + ) return function_to_fdatabasis(f=std_function, new_basis=X.basis) From 0b51aa523e73e492747802bc3218706a343033c1 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 17 Sep 2023 13:48:30 +0200 Subject: [PATCH 157/192] Reorganize std_function for clarity --- skfda/exploratory/stats/_stats.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 4af295b0c..9475e82d6 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -145,12 +145,10 @@ def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: def std_function(t_points: NDArrayFloat) -> NDArrayFloat: basis_evaluation = basis(t_points).reshape((basis.n_basis, -1)) - return np.reshape( - np.sqrt(np.diag( - basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation, - )), - (1, -1, X.dim_codomain), + std_values = np.sqrt( + np.diag(basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation) ) + return np.reshape(std_values, (1, -1, X.dim_codomain)) return function_to_fdatabasis(f=std_function, new_basis=X.basis) From a0d7d7e500a49947a7bca004cc9e43b3ce96ad8d Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 17 Sep 2023 14:03:43 +0200 Subject: [PATCH 158/192] import FDatabasis ignore warning in function_to_fdatabasis --- skfda/_utils/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/_utils/_utils.py b/skfda/_utils/_utils.py index d35617130..aebb5189f 100644 --- a/skfda/_utils/_utils.py +++ b/skfda/_utils/_utils.py @@ -620,7 +620,7 @@ def function_to_fdatabasis( FDataBasis: FDataBasis with calculated coefficients and the new basis. """ - from .. import FDataBasis + from .. import FDataBasis # noqa: WPS442 from ..misc._math import inner_product_matrix if isinstance(f, FDataBasis) and f.basis == new_basis: From 0c11d71f5e2f7646c267f79c74d544702fe67012 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Sun, 17 Sep 2023 14:40:02 +0200 Subject: [PATCH 159/192] Fix style --- skfda/exploratory/stats/_stats.py | 2 +- skfda/tests/test_stats_std.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 9475e82d6..0d6cd01b0 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -146,7 +146,7 @@ def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: def std_function(t_points: NDArrayFloat) -> NDArrayFloat: basis_evaluation = basis(t_points).reshape((basis.n_basis, -1)) std_values = np.sqrt( - np.diag(basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation) + np.diag(basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation), ) return np.reshape(std_values, (1, -1, X.dim_codomain)) diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index f4c10b8a1..ce6945af7 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -20,6 +20,7 @@ # Fixtures for test_std_fdatabasis_vector_valued_basis + @pytest.fixture(params=[3, 5]) def vv_n_basis1(request: Any) -> int: """n_basis for 1st coordinate of vector valued basis.""" From 44cef11214d924fe0f674d48d3b0124138335927 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 20 Sep 2023 13:19:07 +0200 Subject: [PATCH 160/192] Add a version switcher for the docs in the web. --- docs/_static/switcher.json | 13 +++++++++++++ docs/conf.py | 24 ++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 docs/_static/switcher.json diff --git a/docs/_static/switcher.json b/docs/_static/switcher.json new file mode 100644 index 000000000..71b626e80 --- /dev/null +++ b/docs/_static/switcher.json @@ -0,0 +1,13 @@ +[ + { + "name": "dev", + "version": "dev", + "url": "https://fda.readthedocs.io/en/latest/" + }, + { + "name": "0.8.1 (stable)", + "version": "stable", + "url": "https://fda.readthedocs.io/en/stable/", + "preferred": true + } +] \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 80583b65e..f4d270734 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,8 +46,16 @@ "Universidad Autónoma de Madrid" ) github_url = "https://github.com/GAA-UAM/scikit-fda" -rtd_version = os.environ.get("READTHEDOCS_VERSION", "latest") -branch = "master" if rtd_version == "stable" else "develop" +rtd_version = os.environ.get("READTHEDOCS_VERSION") +rtd_version_type = os.environ.get("READTHEDOCS_VERSION_TYPE") + +switcher_version = rtd_version +if switcher_version == "latest": + switcher_version = "dev" +elif rtd_version_type not in ["branch", "tag"]: + switcher_version = skfda.__version__ + +rtd_branch = os.environ.get(" READTHEDOCS_GIT_IDENTIFIER", "develop") language = "en" try: @@ -112,6 +120,14 @@ html_theme_options = { "use_edit_page_button": True, "github_url": github_url, + "switcher": { + "json_url": ( + "https://fda.readthedocs.io/en/latest/_static/switcher.json" + ), + "version_match": switcher_version, + }, + "show_version_warning_banner": True, + "navbar_start": ["navbar-logo", "version-switcher"], "icon_links": [ { "name": "PyPI", @@ -289,7 +305,7 @@ def linkcode_resolve(domain: str, info: Mapping[str, str]) -> str | None: else: linespec = "" - return f"{github_url}/tree/{branch}/skfda/{fn}{linespec}" + return f"{github_url}/tree/{rtd_branch}/skfda/{fn}{linespec}" # -- Options for "sphinx.ext.mathjax" -- @@ -391,7 +407,7 @@ def __repr__(self) -> str: "binder": { "org": "GAA-UAM", "repo": "scikit-fda", - "branch": branch, + "branch": rtd_branch, "binderhub_url": "https://mybinder.org", "dependencies": ["../binder/requirements.txt"], "notebooks_dir": "../examples", From 533d13b571a1529dd2933d95819b8df72016b851 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 20 Sep 2023 15:58:31 +0200 Subject: [PATCH 161/192] Add std to stats.rst --- docs/modules/exploratory/stats.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/modules/exploratory/stats.rst b/docs/modules/exploratory/stats.rst index 17642a732..b55b05fe6 100644 --- a/docs/modules/exploratory/stats.rst +++ b/docs/modules/exploratory/stats.rst @@ -31,4 +31,5 @@ statistics can be used. skfda.exploratory.stats.cov skfda.exploratory.stats.var + skfda.exploratory.stats.std From f3845b5c0e9666cb46419f40f8686ade2922ece2 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 20 Sep 2023 16:01:15 +0200 Subject: [PATCH 162/192] Fix type checking for function_to_fdatabasis --- skfda/_utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/_utils/__init__.py b/skfda/_utils/__init__.py index 838328a6c..bb0636fdd 100644 --- a/skfda/_utils/__init__.py +++ b/skfda/_utils/__init__.py @@ -45,9 +45,9 @@ _same_domain as _same_domain, _to_grid as _to_grid, _to_grid_points as _to_grid_points, + function_to_fdatabasis as function_to_fdatabasis, nquad_vec as nquad_vec, ) - from ._warping import ( invert_warping as invert_warping, normalize_scale as normalize_scale, From 21967db52a97c021692bc073f683b1817a4b4c18 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 20 Sep 2023 16:11:03 +0200 Subject: [PATCH 163/192] Isort --- skfda/exploratory/stats/_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 0d6cd01b0..ca8e88dc2 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -1,10 +1,10 @@ """Functional data descriptive statistics.""" from __future__ import annotations +import functools from builtins import isinstance from typing import Callable, TypeVar, Union -import functools import numpy as np from scipy import integrate from scipy.stats import rankdata From ce105d9dab78ec43307e075c29a496c71b96d44d Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 20 Sep 2023 16:25:13 +0200 Subject: [PATCH 164/192] Fix nested function warning --- skfda/exploratory/stats/_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index ca8e88dc2..40cee7382 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -143,7 +143,7 @@ def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: X.coefficients, rowvar=False, ddof=ddof, ).reshape((basis.n_basis, basis.n_basis)) - def std_function(t_points: NDArrayFloat) -> NDArrayFloat: + def std_function(t_points: NDArrayFloat) -> NDArrayFloat: # noqa: WPS430 basis_evaluation = basis(t_points).reshape((basis.n_basis, -1)) std_values = np.sqrt( np.diag(basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation), From 087365fec8e75acb7b489163029f58c3a1824c3c Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 20 Sep 2023 18:35:17 +0200 Subject: [PATCH 165/192] Fix style. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index f4d270734..e25650181 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,7 +52,7 @@ switcher_version = rtd_version if switcher_version == "latest": switcher_version = "dev" -elif rtd_version_type not in ["branch", "tag"]: +elif rtd_version_type not in {"branch", "tag"}: switcher_version = skfda.__version__ rtd_branch = os.environ.get(" READTHEDOCS_GIT_IDENTIFIER", "develop") From 39ee3ae6ba3e7fac86502e340308b0ec4c46c271 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 20 Sep 2023 19:32:41 +0200 Subject: [PATCH 166/192] Fix Pandas tests with boolean reductions. --- skfda/tests/test_pandas_fdatabasis.py | 16 +++++++++++++++- skfda/tests/test_pandas_fdatagrid.py | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/skfda/tests/test_pandas_fdatabasis.py b/skfda/tests/test_pandas_fdatabasis.py index 6192ee7b7..96be90119 100644 --- a/skfda/tests/test_pandas_fdatabasis.py +++ b/skfda/tests/test_pandas_fdatabasis.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Callable, Iterable, NoReturn +from typing import Any, Callable, Iterable, NoReturn, Sequence import numpy as np import pandas @@ -271,6 +271,20 @@ def all_numeric_reductions(request: Any) -> Any: """ return request.param + +_all_boolean_reductions: Sequence[str] = [ + # "all", + # "any", +] + + +@pytest.fixture(params=_all_boolean_reductions) +def all_boolean_reductions(request: Any) -> Any: + """ + Fixture for boolean reduction names. + """ + return request.param + ############################################################################## # Tests ############################################################################## diff --git a/skfda/tests/test_pandas_fdatagrid.py b/skfda/tests/test_pandas_fdatagrid.py index 47db6c383..80818a1b3 100644 --- a/skfda/tests/test_pandas_fdatagrid.py +++ b/skfda/tests/test_pandas_fdatagrid.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Callable, Generator, NoReturn, Union +from typing import Any, Callable, Generator, NoReturn, Sequence, Union import numpy as np import pandas @@ -290,6 +290,20 @@ def all_numeric_reductions(request: Any) -> Any: """ return request.param + +_all_boolean_reductions: Sequence[str] = [ + # "all", + # "any", +] + + +@pytest.fixture(params=_all_boolean_reductions) +def all_boolean_reductions(request: Any) -> Any: + """ + Fixture for boolean reduction names. + """ + return request.param + ############################################################################## # Tests ############################################################################## From 1644ad0427019b07874173044b371f6fd12ffb6f Mon Sep 17 00:00:00 2001 From: VNMabus Date: Thu, 21 Sep 2023 09:38:05 +0200 Subject: [PATCH 167/192] Fix docs. --- docs/index.rst | 2 +- skfda/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 55cbe94dc..b5ee3c933 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,7 +34,7 @@ Github you can find more information related to the development of the package. :caption: More documentation apilist - glossary + Glossary contributors An exhaustive list of all the contents of the package can be found in the diff --git a/skfda/__init__.py b/skfda/__init__.py index 1a8f41021..f9434fa97 100644 --- a/skfda/__init__.py +++ b/skfda/__init__.py @@ -30,4 +30,4 @@ concatenate as concatenate, ) -__version__ = "0.8.1" +__version__ = "0.9.dev1" From ee676cda04becb33c99907ea0bf7761b499a14b5 Mon Sep 17 00:00:00 2001 From: E105D104U125 Date: Mon, 25 Sep 2023 00:28:39 +0200 Subject: [PATCH 168/192] Update citation and See also in DTMClassifier class --- docs/refs.bib | 15 +++++++++++++++ skfda/ml/classification/_centroid_classifiers.py | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/refs.bib b/docs/refs.bib index cb1255044..c0aedacb8 100644 --- a/docs/refs.bib +++ b/docs/refs.bib @@ -364,6 +364,21 @@ @article{pini++_2018_hotelling keywords = {Functional data,High-dimensional data Hotelling’s,Hilbert space,Nonparametric inference,Permutation test} } +@incollection{pintado+romo_2005_depthbased, + title = {Depth-Based Classification for Functional Data}, + shorttitle = {Data {{Depth}}}, + booktitle = {Data {{Depth}}: {{Robust Multivariate Analysis}}, {{Computational Geometry}} and {{Applications}}}, + author = {Pintado, Sara and Romo, Juan}, + year = {2005}, + month = nov, + series = {{{DIMACS Series}} in {{Discrete Mathematics}} and {{Theoretical Computer Science}}}, + volume = {72}, + pages = {103--119}, + publisher = {{American Mathematical Society}}, + doi = {10.1090/dimacs/072/08}, + isbn = {978-0-8218-3596-8} +} + @inbook{ramsay+silverman_2005_functionala, title = {From Functional Data to Smooth Functions}, booktitle = {Functional Data Analysis}, diff --git a/skfda/ml/classification/_centroid_classifiers.py b/skfda/ml/classification/_centroid_classifiers.py index dd19c2f1a..4bac8ad9e 100644 --- a/skfda/ml/classification/_centroid_classifiers.py +++ b/skfda/ml/classification/_centroid_classifiers.py @@ -127,7 +127,7 @@ class DTMClassifier(NearestCentroid[Input, Target]): Test samples are classified to the class that minimizes the distance of the observation to the trimmed mean of the group - :footcite:`fraiman+muniz_2001_trimmed`. + :footcite:`pintado+romo_2005_depthbased`. Parameters: proportiontocut: @@ -174,6 +174,7 @@ class DTMClassifier(NearestCentroid[Input, Target]): See also: :class:`~skfda.ml.classification.NearestCentroid` + :class:`~skfda.exploratory.stats.trim_mean` References: .. footbibliography:: From 1bac4963e5861f6c769c496a3d0ff6649673d12a Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 4 Oct 2023 13:52:21 +0200 Subject: [PATCH 169/192] Fix message and condition (n_basis < order) in the initialization of a BSplineBasis. Condition is equivalent to previous one. The message has been corrected to match the condition. --- skfda/representation/basis/_bspline_basis.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/skfda/representation/basis/_bspline_basis.py b/skfda/representation/basis/_bspline_basis.py index 7b4898f96..15059e49c 100644 --- a/skfda/representation/basis/_bspline_basis.py +++ b/skfda/representation/basis/_bspline_basis.py @@ -129,11 +129,10 @@ def __init__( # noqa: WPS238 ) n_basis = len(knots) + order - 2 - if (n_basis - order + 2) < 2: + if n_basis < order: raise ValueError( - f"The number of basis ({n_basis}) minus the " - f"order of the bspline ({order}) should be " - f"greater than 3.", + f"The number of basis ({n_basis}) should not be smaller " + f"than the order of the bspline ({order}).", ) self._order = order From 37536a1658c9389b0d65b55229f6ffed76d62f3b Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 4 Oct 2023 14:10:10 +0200 Subject: [PATCH 170/192] Change `ddof` argument name to `correction` --- skfda/exploratory/stats/_stats.py | 20 +++++++-------- skfda/misc/covariances.py | 30 +++++++++++------------ skfda/misc/scoring.py | 2 +- skfda/ml/clustering/_kmeans.py | 2 +- skfda/representation/_functional_data.py | 12 ++++----- skfda/representation/basis/_fdatabasis.py | 25 ++++++++++--------- skfda/representation/grid.py | 24 +++++++++--------- 7 files changed, 58 insertions(+), 57 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 20d2443e2..835eb93e6 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -41,22 +41,22 @@ def mean( return (X * weight).sum() -def var(X: FData, ddof: int = 1) -> FDataGrid: +def var(X: FData, correction: int = 1) -> FDataGrid: """ Compute the variance of a set of samples in a FData object. Args: X: Object containing all the set of samples whose variance is desired. - ddof: "Delta Degrees of Freedom": the divisor used in the calculation - is `N - ddof`, where `N` represents the number of elements. By - default `ddof` is 1. + correction: degrees of freedom adjustment. The divisor used in the + calculation is `N - correction`, where `N` represents the number of + elements. Default: `1`. Returns: Variance of all the samples in the original object, as a :term:`functional data object` with just one sample. """ - return X.var(ddof=ddof) # type: ignore[no-any-return] + return X.var(correction=correction) # type: ignore[no-any-return] def gmean(X: FDataGrid) -> FDataGrid: @@ -76,7 +76,7 @@ def gmean(X: FDataGrid) -> FDataGrid: def cov( X: FData, - ddof: int = 1 + correction: int = 1, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: """ Compute the covariance. @@ -86,9 +86,9 @@ def cov( Args: X: Object containing different samples of a functional variable. - ddof: "Delta Degrees of Freedom": the divisor used in the calculation - is `N - ddof`, where `N` represents the number of elements. By - default `ddof` is 1. + correction: degrees of freedom adjustment. The divisor used in the + calculation is `N - correction`, where `N` represents the number of + elements. Default: `1`. Returns: @@ -96,7 +96,7 @@ def cov( callable. """ - return X.cov(ddof=ddof) + return X.cov(correction=correction) def modified_epigraph_index(X: FDataGrid) -> NDArrayFloat: diff --git a/skfda/misc/covariances.py b/skfda/misc/covariances.py index e74dab770..05d94f21f 100644 --- a/skfda/misc/covariances.py +++ b/skfda/misc/covariances.py @@ -768,27 +768,27 @@ class Empirical(Covariance): The sample covariance function is defined as . math:: - K(t, s) = \frac{1}{N-\text{ddof}}\sum_{n=1}^N\left(x_n(t) - + K(t, s) = \frac{1}{N-\text{correction}}\sum_{n=1}^N\left(x_n(t) - \bar{x}(t)\right) \left(x_n(s) - \bar{x}(s)\right) where :math:`x_n(t)` is the n-th sample and :math:`\bar{x}(t)` is the mean of the samples. :math:`N` is the number of samples, - :math:`\text{ddof}` means "Delta Degrees of Freedom" and is such that - :math:`N-\text{ddof}` is the divisor used in the calculation of the - covariance function. + :math:`\text{correction}` is the degrees of freedom adjustment and is such + that :math:`N-\text{correction}` is the divisor used in the calculation of + the covariance function. """ _latex_formula = ( - r"K(t, s) = \frac{1}{N-\text{ddof}}\sum_{n=1}^N(x_n(t) - \bar{x}(t))" - r"(x_n(s) - \bar{x}(s))" + r"K(t, s) = \frac{1}{N-\text{correction}}\sum_{n=1}^N" + r"(x_n(t) - \bar{x}(t))(x_n(s) - \bar{x}(s))" ) _parameters_str = [ ("data", "data"), ] cov_fdata: FData - ddof: int + correction: int @abc.abstractmethod def __init__(self, data: FData) -> None: @@ -815,17 +815,17 @@ class EmpiricalGrid(Empirical): """Sample covariance function for FDataGrid.""" cov_fdata: FDataGrid - ddof: int + correction: int - def __init__(self, data: FDataGrid, ddof: int = 1) -> None: + def __init__(self, data: FDataGrid, correction: int = 1) -> None: super().__init__(data=data) - self.ddof = ddof + self.correction = correction self.cov_fdata = data.copy( data_matrix=np.cov( data.data_matrix[..., 0], rowvar=False, - ddof=ddof, + ddof=correction, )[np.newaxis, ...], grid_points=[ data.grid_points[0], @@ -851,16 +851,16 @@ class EmpiricalBasis(Empirical): cov_fdata: FDataBasis coeff_matrix: NDArrayFloat - ddof: int + correction: int - def __init__(self, data: FDataBasis, ddof: int = 1) -> None: + def __init__(self, data: FDataBasis, correction: int = 1) -> None: super().__init__(data=data) - self.ddof = ddof + self.correction = correction self.coeff_matrix = np.cov( data.coefficients, rowvar=False, - ddof=ddof, + ddof=correction, ) self.cov_fdata = FDataBasis( basis=TensorBasis([data.basis, data.basis]), diff --git a/skfda/misc/scoring.py b/skfda/misc/scoring.py index 419d25c0f..7491b92ff 100644 --- a/skfda/misc/scoring.py +++ b/skfda/misc/scoring.py @@ -74,7 +74,7 @@ def _var( from ..exploratory.stats import mean, var if weights is None: - return var(x, ddof=0) + return var(x, correction=0) return mean( # type: ignore[no-any-return] np.power(x - mean(x, weights=weights), 2), diff --git a/skfda/ml/clustering/_kmeans.py b/skfda/ml/clustering/_kmeans.py index 9d58e068a..c433eb186 100644 --- a/skfda/ml/clustering/_kmeans.py +++ b/skfda/ml/clustering/_kmeans.py @@ -147,7 +147,7 @@ def _check_clustering(self, fdata: Input) -> Input: return fdata def _tolerance(self, fdata: Input) -> float: - variance = fdata.var(ddof=0) + variance = fdata.var(correction=0) mean_variance = np.mean(variance[0].data_matrix) return float(mean_variance * self.tol) diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index 2696e7b25..f62e8ccde 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -826,7 +826,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, - ddof: int = 1, + correction: int = 1, ) -> NDArrayFloat: pass @@ -834,7 +834,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, - ddof: int = 1, + correction: int = 1, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -844,7 +844,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, - ddof: int = 1, + correction: int = 1, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -864,9 +864,9 @@ def cov( # noqa: WPS320, WPS451 Args: s_points: Points where the covariance function is evaluated. t_points: Points where the covariance function is evaluated. - ddof: "Delta Degrees of Freedom": the divisor used in the - calculation is `N - ddof`, where `N` represents the number - of elements. By default `ddof` is 1. + correction: degrees of freedom adjustment. The divisor used in the + calculation is `N - correction`, where `N` represents the + number of elements. Default: `1`. Returns: Covariance function. diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 8289471d4..12608dda3 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -445,7 +445,7 @@ def sum( # noqa: WPS125 def var( self: T, eval_points: Optional[NDArrayFloat] = None, - ddof: int = 1, + correction: int = 1, ) -> T: """Compute the variance of the functional data object. @@ -460,15 +460,16 @@ def var( numpy.linspace with bounds equal to the ones defined in self.domain_range and the number of points the maximum between 501 and 10 times the number of basis. - ddof: "Delta Degrees of Freedom": the divisor used in the - calculation is `N - ddof`, where `N` represents the number of - elements. By default `ddof` is 1. + correction: degrees of freedom adjustment. The divisor used in the + calculation is `N - correction`, where `N` represents the + number of elements. Default: `1`. Returns: Variance of the original object. """ - return self.to_grid(eval_points).var(ddof=ddof).to_basis(self.basis) + return self.to_grid( + eval_points).var(correction=correction).to_basis(self.basis) @overload def cov( # noqa: WPS451 @@ -476,7 +477,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, - ddof: int = 1, + correction: int = 1, ) -> NDArrayFloat: pass @@ -484,7 +485,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, - ddof: int = 1, + correction: int = 1, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -493,7 +494,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, - ddof: int = 1, + correction: int = 1, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -515,9 +516,9 @@ def cov( # noqa: WPS320, WPS451 Args: s_points: Points where the covariance function is evaluated. t_points: Points where the covariance function is evaluated. - ddof: "Delta Degrees of Freedom": the divisor used in the - calculation is `N - ddof`, where `N` represents the number - of elements. By default `ddof` is 1. + correction: degrees of freedom adjustment. The divisor used in the + calculation is `N - correction`, where `N` represents the + number of elements. Default: `1`. Returns: Covariance function. @@ -525,7 +526,7 @@ def cov( # noqa: WPS320, WPS451 """ # To avoid circular imports from ...misc.covariances import EmpiricalBasis - cov_function = EmpiricalBasis(self, ddof=ddof) + cov_function = EmpiricalBasis(self, correction=correction) if s_points is None or t_points is None: return cov_function return cov_function(s_points, t_points) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 5801d3a01..f4597c7eb 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -582,13 +582,13 @@ def sum( # noqa: WPS125 sample_names=(None,), ) - def var(self: T, ddof: int = 1) -> T: + def var(self: T, correction: int = 1) -> T: """Compute the variance of a set of samples in a FDataGrid object. Args: - ddof: "Delta Degrees of Freedom": the divisor used in the - calculation is `N - ddof`, where `N` represents the number of - elements. By default `ddof` is 1. + correction: "Delta Degrees of Freedom": the divisor used in the + calculation is `N - correction`, where `N` represents the number of + elements. By default `correction` is 1. Returns: A FDataGrid object with just one sample representing the @@ -599,7 +599,7 @@ def var(self: T, ddof: int = 1) -> T: data_matrix=np.array([np.var( self.data_matrix, axis=0, - ddof=ddof, + ddof=correction, )]), sample_names=("variance",), ) @@ -610,7 +610,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, - ddof: int = 1, + correction: int = 1, ) -> NDArrayFloat: pass @@ -618,7 +618,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, - ddof: int = 1, + correction: int = 1, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -627,7 +627,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, - ddof: int = 1, + correction: int = 1, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -643,9 +643,9 @@ def cov( # noqa: WPS320, WPS451 Args: s_points: Grid points where the covariance function is evaluated. t_points: Grid points where the covariance function is evaluated. - ddof: "Delta Degrees of Freedom": the divisor used in the - calculation is `N - ddof`, where `N` represents the number - of elements. By default `ddof` is 1. + correction: degrees of freedom adjustment. The divisor used in the + calculation is `N - correction`, where `N` represents the + number of elements. Default: `1`. Returns: Covariance function. @@ -653,7 +653,7 @@ def cov( # noqa: WPS320, WPS451 """ # To avoid circular imports from ..misc.covariances import EmpiricalGrid - cov_function = EmpiricalGrid(self, ddof=ddof) + cov_function = EmpiricalGrid(self, correction=correction) if s_points is None or t_points is None: return cov_function return cov_function(s_points, t_points) From 025f8c021a705acf56b36ab6c05cce01a13f4b67 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 4 Oct 2023 14:19:41 +0200 Subject: [PATCH 171/192] Fix trailing whitespace --- skfda/representation/basis/_fdatabasis.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index 12608dda3..ae4ef0813 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -461,7 +461,7 @@ def var( self.domain_range and the number of points the maximum between 501 and 10 times the number of basis. correction: degrees of freedom adjustment. The divisor used in the - calculation is `N - correction`, where `N` represents the + calculation is `N - correction`, where `N` represents the number of elements. Default: `1`. Returns: @@ -469,7 +469,8 @@ def var( """ return self.to_grid( - eval_points).var(correction=correction).to_basis(self.basis) + eval_points, + ).var(correction=correction).to_basis(self.basis) @overload def cov( # noqa: WPS451 From 5509c0022b02c6ec9ca602d43d81ef026b44c65e Mon Sep 17 00:00:00 2001 From: pcuestas Date: Wed, 4 Oct 2023 14:56:59 +0200 Subject: [PATCH 172/192] Fix tests, remove type from std.register --- skfda/exploratory/stats/_stats.py | 4 ++-- skfda/tests/test_stats_std.py | 33 ++++++------------------------- 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 40cee7382..af0bb1da0 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -124,7 +124,7 @@ def std(X: F, ddof: int = 1) -> F: raise NotImplementedError("Not implemented for this type") -@std.register(FDataGrid) +@std.register def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: """Compute the standard deviation of a FDataGrid.""" return X.copy( @@ -133,7 +133,7 @@ def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: ) -@std.register(FDataBasis) +@std.register def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: """Compute the standard deviation of a FDataBasis.""" from ..._utils import function_to_fdatabasis diff --git a/skfda/tests/test_stats_std.py b/skfda/tests/test_stats_std.py index ce6945af7..24f6687af 100644 --- a/skfda/tests/test_stats_std.py +++ b/skfda/tests/test_stats_std.py @@ -11,7 +11,6 @@ from skfda.exploratory.stats import std from skfda.representation.basis import ( Basis, - BSplineBasis, FourierBasis, MonomialBasis, TensorBasis, @@ -27,25 +26,17 @@ def vv_n_basis1(request: Any) -> int: return request.param # type: ignore[no-any-return] -@pytest.fixture(params=[3]) -def vv_n_basis2(request: Any) -> int: - """n_basis for 2nd coordinate of vector valued basis.""" - return request.param # type: ignore[no-any-return] - - @pytest.fixture def vv_basis1(vv_n_basis1: int) -> Basis: """1-dimensional basis to test for vector valued basis.""" # First element of the basis is assumed to be the 1 function - return BSplineBasis( - n_basis=vv_n_basis1, - domain_range=(0, 1), - order=vv_n_basis1 - 1, + return MonomialBasis( + n_basis=vv_n_basis1, domain_range=(0, 1), ) @pytest.fixture(params=[FourierBasis, MonomialBasis]) -def vv_basis2(request: Any, vv_n_basis2: int) -> Basis: +def vv_basis2(request: Any, vv_n_basis2: int = 3) -> Basis: """1-dimensional basis to test for vector valued basis.""" # First element of the basis is assumed to be the 1 function return request.param( # type: ignore[no-any-return] @@ -55,29 +46,17 @@ def vv_basis2(request: Any, vv_n_basis2: int) -> Basis: # Fixtures for test_std_fdatabasis_tensor_basis -@pytest.fixture(params=[3]) -def t_n_basis1(request: Any) -> int: - """n_basis for 1st input argument of tensor basis.""" - return request.param # type: ignore[no-any-return] - - -@pytest.fixture(params=[5]) -def t_n_basis2(request: Any) -> int: - """n_basis for 2nd input argument of tensor basis.""" - return request.param # type: ignore[no-any-return] - - @pytest.fixture(params=[FourierBasis]) -def t_basis1(request: Any, t_n_basis2: int) -> Basis: +def t_basis1(request: Any, t_n_basis1: int = 3) -> Basis: """1-dimensional basis to test for tensor basis.""" # First element of the basis is assumed to be the 1 function return request.param( # type: ignore[no-any-return] - domain_range=(0, 1), n_basis=t_n_basis2, + domain_range=(0, 1), n_basis=t_n_basis1, ) @pytest.fixture(params=[MonomialBasis]) -def t_basis2(request: Any, t_n_basis2: int) -> Basis: +def t_basis2(request: Any, t_n_basis2: int = 5) -> Basis: """1-dimensional basis to test for tensor basis.""" # First element of the basis is assumed to be the 1 function return request.param( # type: ignore[no-any-return] From d75b60ba49c874fd512b243ffd08ebf958cb327c Mon Sep 17 00:00:00 2001 From: VNMabus Date: Mon, 9 Oct 2023 11:41:52 +0200 Subject: [PATCH 173/192] Change simps for simpson. Extract also average_function_value. --- skfda/_utils/ndfunction/__init__.py | 0 skfda/_utils/ndfunction/_functions.py | 116 ++++++++++++++++++ skfda/exploratory/depth/_depth.py | 2 +- .../outliers/_directional_outlyingness.py | 4 +- skfda/exploratory/stats/_fisher_rao.py | 10 +- skfda/exploratory/stats/_stats.py | 2 +- skfda/misc/_math.py | 2 +- skfda/misc/metrics/_fisher_rao.py | 6 +- skfda/misc/metrics/_lp_norms.py | 2 +- .../ml/regression/_historical_linear_model.py | 2 +- skfda/preprocessing/dim_reduction/_fpca.py | 2 +- .../feature_construction/_functions.py | 73 +++-------- skfda/representation/grid.py | 2 +- 13 files changed, 153 insertions(+), 70 deletions(-) create mode 100644 skfda/_utils/ndfunction/__init__.py create mode 100644 skfda/_utils/ndfunction/_functions.py diff --git a/skfda/_utils/ndfunction/__init__.py b/skfda/_utils/ndfunction/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/skfda/_utils/ndfunction/_functions.py b/skfda/_utils/ndfunction/_functions.py new file mode 100644 index 000000000..f65747085 --- /dev/null +++ b/skfda/_utils/ndfunction/_functions.py @@ -0,0 +1,116 @@ +"""Functions for working with arrays of functions.""" +from __future__ import annotations + +import math +from typing import ( + TYPE_CHECKING, + Any, + Literal, + Protocol, + TypeVar, + runtime_checkable, +) + +from ...misc.validation import validate_domain_range +from .. import nquad_vec + +if TYPE_CHECKING: + from ...typing._numpy import NDArrayFloat + from ...representation import FData + from ...typing._base import DomainRangeLike + + +UfuncMethod = Literal[ + "__call__", + "reduce", + "reduceat", + "accumulate", + "outer", + "inner", +] + + +class _SupportsArrayUFunc(Protocol): + def __array_ufunc__( + self, + ufunc: Any, + method: UfuncMethod, + *inputs: Any, + **kwargs: Any, + ) -> Any: + pass + + +T = TypeVar("T", bound=_SupportsArrayUFunc) + + +class _UnaryUfunc(Protocol): + + def __call__(self, __arg: T) -> T: # noqa: WPS112 + pass + + +def _average_function_ufunc( + data: FData, + ufunc: _UnaryUfunc, + *, + domain: DomainRangeLike | None = None, +) -> NDArrayFloat: + + if domain is None: + domain = data.domain_range + else: + domain = validate_domain_range(domain) + + lebesgue_measure = math.prod( + ( + (iterval[1] - iterval[0]) + for iterval in domain + ), + ) + + try: + data_eval = ufunc(data) + except TypeError: + + def integrand(*args: NDArrayFloat) -> NDArrayFloat: # noqa: WPS430 + f1 = data(args)[:, 0, :] + return ufunc(f1) + + return nquad_vec( + integrand, + domain, + ) / lebesgue_measure + + else: + return data_eval.integrate(domain=domain) / lebesgue_measure + + +def average_function_value( + data: FData, + *, + domain: DomainRangeLike | None = None, +) -> NDArrayFloat: + r""" + Calculate the average function value for each function. + + This is the value that, if integrated over the whole domain of each + function, has the same integral as the function itself. + + .. math:: + \bar{x} = \frac{1}{\text{Vol}(\mathcal{T})}\int_{\mathcal{T}} x(t) dt + + Args: + data: Functions where we want to calculate the expected value. + domain: Integration domain. By default, the whole domain is used. + + Returns: + ndarray of shape (n_dimensions, n_samples) with the values of the + expectations. + + See also: + `Entry on Wikipedia + `_ + + """ + return _average_function_ufunc(data, ufunc=lambda x: x, domain=domain) diff --git a/skfda/exploratory/depth/_depth.py b/skfda/exploratory/depth/_depth.py index a0d27c2e5..dcacd8446 100644 --- a/skfda/exploratory/depth/_depth.py +++ b/skfda/exploratory/depth/_depth.py @@ -92,7 +92,7 @@ def transform(self, X: FDataGrid) -> NDArrayFloat: # noqa: D102 integrand = pointwise_depth for d, s in zip(X.domain_range, X.grid_points): - integrand = scipy.integrate.simps( + integrand = scipy.integrate.simpson( integrand, x=s, axis=1, diff --git a/skfda/exploratory/outliers/_directional_outlyingness.py b/skfda/exploratory/outliers/_directional_outlyingness.py index 30e8338d3..675d149c6 100644 --- a/skfda/exploratory/outliers/_directional_outlyingness.py +++ b/skfda/exploratory/outliers/_directional_outlyingness.py @@ -213,7 +213,7 @@ def directional_outlyingness_stats( # noqa: WPS218 ) assert weighted_dir_outlyingness.shape == dir_outlyingness.shape - mean_dir_outlyingness = scipy.integrate.simps( + mean_dir_outlyingness = scipy.integrate.simpson( weighted_dir_outlyingness, fdatagrid.grid_points[0], axis=1, @@ -230,7 +230,7 @@ def directional_outlyingness_stats( # noqa: WPS218 axis=-1, )) weighted_norm = norm * pointwise_weights - variation_dir_outlyingness = scipy.integrate.simps( + variation_dir_outlyingness = scipy.integrate.simpson( weighted_norm, fdatagrid.grid_points[0], axis=1, diff --git a/skfda/exploratory/stats/_fisher_rao.py b/skfda/exploratory/stats/_fisher_rao.py index c897b13fc..5a3ffe6c9 100644 --- a/skfda/exploratory/stats/_fisher_rao.py +++ b/skfda/exploratory/stats/_fisher_rao.py @@ -133,7 +133,7 @@ def _fisher_rao_warping_mean( # Compute shooting vectors for psi_i in psi_data: - inner = scipy.integrate.simps(mu * psi_i, x=eval_points) + inner = scipy.integrate.simpson(mu * psi_i, x=eval_points) inner = max(min(inner, 1), -1) theta = np.arccos(inner) @@ -143,7 +143,7 @@ def _fisher_rao_warping_mean( # Mean of shooting vectors vmean /= warping.n_samples - v_norm = np.sqrt(scipy.integrate.simps(np.square(vmean))) + v_norm = np.sqrt(scipy.integrate.simpson(np.square(vmean))) # Convergence criterion if v_norm < tol: @@ -266,7 +266,7 @@ def fisher_rao_karcher_mean( # Initialize with function closest to the L2 mean with the L2 distance centered = (srsf.T - srsf.mean(axis=0, keepdims=True).T).T - distances = scipy.integrate.simps( + distances = scipy.integrate.simpson( np.square(centered, out=centered), eval_points_normalized, axis=1, @@ -304,14 +304,14 @@ def fisher_rao_karcher_mean( # Convergence criterion mu_norm = np.sqrt( - scipy.integrate.simps( + scipy.integrate.simpson( np.square(mu, out=mu_aux), eval_points_normalized, ), ) mu_diff = np.sqrt( - scipy.integrate.simps( + scipy.integrate.simpson( np.square(mu - mu_1, out=mu_aux), eval_points_normalized, ), diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 835eb93e6..9acaefeb7 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -124,7 +124,7 @@ def modified_epigraph_index(X: FDataGrid) -> NDArrayFloat: integrand = num_functions_above for d, s in zip(X.domain_range, X.grid_points): - integrand = integrate.simps( + integrand = integrate.simpson( integrand, x=s, axis=1, diff --git a/skfda/misc/_math.py b/skfda/misc/_math.py index 4e02de2f8..94b440504 100644 --- a/skfda/misc/_math.py +++ b/skfda/misc/_math.py @@ -363,7 +363,7 @@ def _inner_product_fdatagrid( # Perform quadrature inside the einsum for i, s in enumerate(arg1.grid_points[::-1]): identity = np.eye(len(s)) - weights = scipy.integrate.simps(identity, x=s) + weights = scipy.integrate.simpson(identity, x=s) index = (slice(None),) + (np.newaxis,) * (i + 1) d1 *= weights[index] diff --git a/skfda/misc/metrics/_fisher_rao.py b/skfda/misc/metrics/_fisher_rao.py index 7fd18e4da..bfc4e2f89 100644 --- a/skfda/misc/metrics/_fisher_rao.py +++ b/skfda/misc/metrics/_fisher_rao.py @@ -234,7 +234,7 @@ def fisher_rao_amplitude_distance( penalty = np.sqrt(penalty, out=penalty) penalty -= 1 penalty = np.square(penalty, out=penalty) - penalty = scipy.integrate.simps(penalty, x=eval_points_normalized) + penalty = scipy.integrate.simpson(penalty, x=eval_points_normalized) distance = np.sqrt(distance**2 + lam * penalty) @@ -322,7 +322,7 @@ def fisher_rao_phase_distance( derivative_warping = np.sqrt(derivative_warping, out=derivative_warping) - d = scipy.integrate.simps(derivative_warping, x=eval_points_normalized) + d = scipy.integrate.simpson(derivative_warping, x=eval_points_normalized) d = np.clip(d, -1, 1) return np.arccos(d) # type: ignore[no-any-return] @@ -394,7 +394,7 @@ def _fisher_rao_warping_distance( product = np.multiply(srsf_warping1, srsf_warping2, out=srsf_warping1) - d = scipy.integrate.simps(product, x=warping1.grid_points[0]) + d = scipy.integrate.simpson(product, x=warping1.grid_points[0]) d = np.clip(d, -1, 1) return np.arccos(d) # type: ignore[no-any-return] diff --git a/skfda/misc/metrics/_lp_norms.py b/skfda/misc/metrics/_lp_norms.py index 2a6c2f9a6..e0cd2904f 100644 --- a/skfda/misc/metrics/_lp_norms.py +++ b/skfda/misc/metrics/_lp_norms.py @@ -161,7 +161,7 @@ def __call__(self, vector: Union[NDArrayFloat, FData]) -> NDArrayFloat: # Computes the norm, approximating the integral with Simpson's # rule. - res = scipy.integrate.simps( + res = scipy.integrate.simpson( data_matrix[..., 0] ** self.p, x=vector.grid_points, ) ** (1 / self.p) diff --git a/skfda/ml/regression/_historical_linear_model.py b/skfda/ml/regression/_historical_linear_model.py index de5dc38bf..21377c77c 100644 --- a/skfda/ml/regression/_historical_linear_model.py +++ b/skfda/ml/regression/_historical_linear_model.py @@ -42,7 +42,7 @@ def _pairwise_fem_inner_product( eval_fd = fd(grid) prod = eval_fem * eval_fd - integral = scipy.integrate.simps(prod, grid, axis=1) + integral = scipy.integrate.simpson(prod, grid, axis=1) return np.sum(integral, axis=-1) # type: ignore[no-any-return] diff --git a/skfda/preprocessing/dim_reduction/_fpca.py b/skfda/preprocessing/dim_reduction/_fpca.py index f5c88a54a..9110f31c0 100644 --- a/skfda/preprocessing/dim_reduction/_fpca.py +++ b/skfda/preprocessing/dim_reduction/_fpca.py @@ -348,7 +348,7 @@ def _fit_grid( if self._weights is None: # grid_points is a list with one array in the 1D case identity = np.eye(len(X.grid_points[0])) - self._weights = scipy.integrate.simps(identity, X.grid_points[0]) + self._weights = scipy.integrate.simpson(identity, X.grid_points[0]) elif callable(self._weights): self._weights = self._weights(X.grid_points[0]) # if its a FDataGrid then we need to reduce the dimension to 1-D diff --git a/skfda/preprocessing/feature_construction/_functions.py b/skfda/preprocessing/feature_construction/_functions.py index 3359a77af..e426afaa4 100644 --- a/skfda/preprocessing/feature_construction/_functions.py +++ b/skfda/preprocessing/feature_construction/_functions.py @@ -3,13 +3,16 @@ from __future__ import annotations import itertools -from typing import Optional, Sequence, Tuple, TypeVar, Union, cast, overload +from typing import Optional, Sequence, Tuple, TypeVar, Union, cast import numpy as np -from typing_extensions import Literal, Protocol, TypeGuard +from typing_extensions import Literal, TypeGuard -from ..._utils import nquad_vec -from ...misc.validation import check_fdata_dimensions, validate_domain_range +from ..._utils.ndfunction._functions import ( + _average_function_ufunc, + _UnaryUfunc, +) +from ...misc.validation import check_fdata_dimensions from ...representation import FData, FDataBasis, FDataGrid from ...typing._base import DomainRangeLike from ...typing._numpy import ArrayLike, NDArrayBool, NDArrayFloat, NDArrayInt @@ -17,20 +20,6 @@ T = TypeVar("T", bound=Union[NDArrayFloat, FDataGrid]) -class _BasicUfuncProtocol(Protocol): - - @overload - def __call__(self, __arg: FDataGrid) -> FDataGrid: # noqa: WPS112 - pass - - @overload - def __call__(self, __arg: NDArrayFloat) -> NDArrayFloat: # noqa: WPS112 - pass - - def __call__(self, __arg: T) -> T: # noqa: WPS112 - pass - - def _sequence_of_ints(data: Sequence[object]) -> TypeGuard[Sequence[int]]: """Check that every element is an integer.""" return all(isinstance(d, int) for d in data) @@ -332,7 +321,7 @@ def number_crossings( >>> from skfda.preprocessing.feature_construction import ( ... number_crossings, - ... ) + ... ) >>> from scipy.special import jv >>> import numpy as np >>> x_grid = np.linspace(0, 14, 14) @@ -521,7 +510,7 @@ def unconditional_moment( def unconditional_expected_value( data: FData, - function: _BasicUfuncProtocol, + function: _UnaryUfunc, *, domain: DomainRangeLike | None = None, ) -> NDArrayFloat: @@ -537,19 +526,19 @@ def unconditional_expected_value( f_p(x(t))=\frac{1}{\left( b-a\right)}\int_a^b g \left(x_p(t)\right) dt - Args: - data: FDataGrid or FDataBasis where we want to calculate - the expected value. - function: function that specifies how the expected value to is - calculated. It has to be a function of X(t). - domain: Integration domain. By default, the whole domain is used. + Args: + data: FDataGrid or FDataBasis where we want to calculate + the expected value. + function: function that specifies how the expected value to is + calculated. It has to be a function of X(t). + domain: Integration domain. By default, the whole domain is used. - Returns: - ndarray of shape (n_dimensions, n_samples) with the values of the - expectations. + Returns: + ndarray of shape (n_dimensions, n_samples) with the values of the + expectations. Examples: - We will use this funtion to calculate the logarithmic first moment + We will use this function to calculate the logarithmic first moment of the first 5 samples of the Berkeley Growth dataset. We will start by importing it. @@ -574,26 +563,4 @@ def unconditional_expected_value( [ 4.84]]) """ - if domain is None: - domain = data.domain_range - else: - domain = validate_domain_range(domain) - - lebesgue_measure = np.prod( - [ - (iterval[1] - iterval[0]) - for iterval in domain - ], - ) - - if isinstance(data, FDataGrid): - return function(data).integrate(domain=domain) / lebesgue_measure - - def integrand(*args: NDArrayFloat) -> NDArrayFloat: # noqa: WPS430 - f1 = data(args)[:, 0, :] - return function(f1) - - return nquad_vec( - integrand, - domain, - ) / lebesgue_measure + return _average_function_ufunc(data, ufunc=function, domain=domain) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index f4597c7eb..fc820c935 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -515,7 +515,7 @@ def integrate( integrand = data.data_matrix for g in data.grid_points[::-1]: - integrand = scipy.integrate.simps( + integrand = scipy.integrate.simpson( integrand, x=g, axis=-2, From cc486a34af34f62348a0e45da2eefac741f1134f Mon Sep 17 00:00:00 2001 From: VNMabus Date: Mon, 9 Oct 2023 14:31:03 +0200 Subject: [PATCH 174/192] Remove some direct uses of Simpson quadrature. --- skfda/_utils/ndfunction/__init__.py | 18 +++++ skfda/_utils/ndfunction/_functions.py | 9 +-- skfda/exploratory/depth/_depth.py | 22 ++---- .../outliers/_directional_outlyingness.py | 69 +++++++++++-------- skfda/representation/grid.py | 5 +- 5 files changed, 65 insertions(+), 58 deletions(-) diff --git a/skfda/_utils/ndfunction/__init__.py b/skfda/_utils/ndfunction/__init__.py index e69de29bb..aa0d1fe33 100644 --- a/skfda/_utils/ndfunction/__init__.py +++ b/skfda/_utils/ndfunction/__init__.py @@ -0,0 +1,18 @@ +from typing import TYPE_CHECKING + +import lazy_loader as lazy + +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "_functions": [ + "average_function_value", + ], + }, +) + +if TYPE_CHECKING: + + from ._functions import ( + average_function_value as average_function_value, + ) diff --git a/skfda/_utils/ndfunction/_functions.py b/skfda/_utils/ndfunction/_functions.py index f65747085..8f7c9b980 100644 --- a/skfda/_utils/ndfunction/_functions.py +++ b/skfda/_utils/ndfunction/_functions.py @@ -2,14 +2,7 @@ from __future__ import annotations import math -from typing import ( - TYPE_CHECKING, - Any, - Literal, - Protocol, - TypeVar, - runtime_checkable, -) +from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeVar from ...misc.validation import validate_domain_range from .. import nquad_vec diff --git a/skfda/exploratory/depth/_depth.py b/skfda/exploratory/depth/_depth.py index dcacd8446..68e51dee8 100644 --- a/skfda/exploratory/depth/_depth.py +++ b/skfda/exploratory/depth/_depth.py @@ -11,9 +11,9 @@ from typing import TypeVar import numpy as np -import scipy.integrate from ..._utils._sklearn_adapter import BaseEstimator +from ..._utils.ndfunction import average_function_value from ...misc.metrics import l2_distance from ...misc.metrics._utils import _fit_metric from ...representation import FData, FDataGrid @@ -82,25 +82,11 @@ def fit( # noqa: D102 def transform(self, X: FDataGrid) -> NDArrayFloat: # noqa: D102 - pointwise_depth = self.multivariate_depth_.transform(X.data_matrix) - - interval_len = ( - self._domain_range[0][1] - - self._domain_range[0][0] + pointwise_depth = X.copy( + data_matrix=self.multivariate_depth_.transform(X.data_matrix), ) - integrand = pointwise_depth - - for d, s in zip(X.domain_range, X.grid_points): - integrand = scipy.integrate.simpson( - integrand, - x=s, - axis=1, - ) - interval_len = d[1] - d[0] - integrand /= interval_len - - return integrand + return average_function_value(pointwise_depth).ravel() @property def max(self) -> float: diff --git a/skfda/exploratory/outliers/_directional_outlyingness.py b/skfda/exploratory/outliers/_directional_outlyingness.py index 675d149c6..91ac8ee67 100644 --- a/skfda/exploratory/outliers/_directional_outlyingness.py +++ b/skfda/exploratory/outliers/_directional_outlyingness.py @@ -98,7 +98,7 @@ def directional_outlyingness_stats( # noqa: WPS218 Method used to order the data. Defaults to :func:`projection depth `. pointwise_weights (array_like, optional): an array containing the - weights of each point of discretisation where values have been + weights of each point of discretization where values have been recorded. Defaults to the same weight for each of the points: 1/len(interval). @@ -183,58 +183,67 @@ def directional_outlyingness_stats( # noqa: WPS218 fdatagrid.domain_range[0][1] - fdatagrid.domain_range[0][0] ) + if not isinstance(pointwise_weights, FDataGrid): + pointwise_weights = fdatagrid.copy( + data_matrix=pointwise_weights, + sample_names=(None,), + ) + depth_pointwise = multivariate_depth(fdatagrid.data_matrix) assert depth_pointwise.shape == fdatagrid.data_matrix.shape[:-1] + depth_pointwise_fd = fdatagrid.copy( + data_matrix=depth_pointwise, + ) + # Obtaining the pointwise median sample Z, to calculate # v(t) = {X(t) − Z(t)}/|| X(t) − Z(t) || median_index = np.argmax(depth_pointwise, axis=0) - pointwise_median = fdatagrid.data_matrix[ - median_index, - range(fdatagrid.data_matrix.shape[1]), - ] - assert pointwise_median.shape == fdatagrid.data_matrix.shape[1:] - v = fdatagrid.data_matrix - pointwise_median - assert v.shape == fdatagrid.data_matrix.shape - v_norm = la.norm(v, axis=-1, keepdims=True) + pointwise_median = fdatagrid.copy( + data_matrix=fdatagrid.data_matrix[ + median_index, + range(fdatagrid.data_matrix.shape[1]), + ][np.newaxis], + sample_names=(None,), + ) + + v = fdatagrid - pointwise_median + v_norm = la.norm(v.data_matrix, axis=-1, keepdims=True) # To avoid ZeroDivisionError, the zeros are substituted by ones (the # reference implementation also does this). v_norm[np.where(v_norm == 0)] = 1 - v_unitary = v / v_norm + v.data_matrix /= v_norm - # Calculation directinal outlyingness - dir_outlyingness = (1 / depth_pointwise[..., np.newaxis] - 1) * v_unitary + # Calculation directional outlyingness + dir_outlyingness = ( + 1 / depth_pointwise_fd - 1 + ) * v # Calculation mean directional outlyingness weighted_dir_outlyingness = ( - dir_outlyingness * pointwise_weights[:, np.newaxis] + dir_outlyingness * pointwise_weights ) - assert weighted_dir_outlyingness.shape == dir_outlyingness.shape - mean_dir_outlyingness = scipy.integrate.simpson( - weighted_dir_outlyingness, - fdatagrid.grid_points[0], - axis=1, - ) + mean_dir_outlyingness = weighted_dir_outlyingness.integrate() assert mean_dir_outlyingness.shape == ( fdatagrid.n_samples, fdatagrid.dim_codomain, ) # Calculation variation directional outlyingness - norm = np.square(la.norm( - dir_outlyingness - - mean_dir_outlyingness[:, np.newaxis, :], - axis=-1, - )) - weighted_norm = norm * pointwise_weights - variation_dir_outlyingness = scipy.integrate.simpson( - weighted_norm, - fdatagrid.grid_points[0], - axis=1, + norm = dir_outlyingness.copy( + data_matrix=np.square( + la.norm( + dir_outlyingness.data_matrix + - mean_dir_outlyingness[:, np.newaxis, :], + axis=-1, + ) + ) ) + weighted_norm = norm * pointwise_weights + variation_dir_outlyingness = weighted_norm.integrate().ravel() assert variation_dir_outlyingness.shape == (fdatagrid.n_samples,) functional_dir_outlyingness = ( @@ -244,7 +253,7 @@ def directional_outlyingness_stats( # noqa: WPS218 assert functional_dir_outlyingness.shape == (fdatagrid.n_samples,) return DirectionalOutlyingnessStats( - directional_outlyingness=dir_outlyingness, + directional_outlyingness=dir_outlyingness.data_matrix, functional_directional_outlyingness=functional_dir_outlyingness, mean_directional_outlyingness=mean_dir_outlyingness, variation_directional_outlyingness=variation_dir_outlyingness, diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index fc820c935..9aae66ac8 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -734,8 +734,9 @@ def _get_op_matrix( return other[other_index] raise ValueError( - f"Invalid dimensions in operator between FDataGrid and Numpy " - f"array: {other.shape}" + f"Invalid dimensions in operator between " + f"FDataGrid (data_matrix.shape={self.data_matrix.shape}) " + f"and Numpy array (shape={other.shape})", ) elif isinstance(other, FDataGrid): From ebab51dbf57f3f76c0df615d830868425366f425 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 10 Oct 2023 10:21:12 +0200 Subject: [PATCH 175/192] Support Lp norms for surfaces. Remove more Simpson quadrature calls. --- skfda/exploratory/stats/_stats.py | 39 ++++++++++---------------- skfda/misc/metrics/_lp_norms.py | 46 ++++++++++++++++--------------- skfda/tests/test_metrics.py | 9 +++--- 3 files changed, 43 insertions(+), 51 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 9acaefeb7..bc40b518c 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -8,6 +8,8 @@ from scipy import integrate from scipy.stats import rankdata +from skfda._utils.ndfunction import average_function_value + from ...misc.metrics._lp_distances import l2_distance from ...representation import FData, FDataGrid from ...typing._metric import Metric @@ -108,33 +110,20 @@ def modified_epigraph_index(X: FDataGrid) -> NDArrayFloat: with all the other curves of our dataset. """ - interval_len = ( - X.domain_range[0][1] - - X.domain_range[0][0] - ) - - # Array containing at each point the number of curves + # Functions containing at each point the number of curves # are above it. - num_functions_above: NDArrayFloat = rankdata( - -X.data_matrix, - method='max', - axis=0, - ) - 1 - - integrand = num_functions_above - - for d, s in zip(X.domain_range, X.grid_points): - integrand = integrate.simpson( - integrand, - x=s, - axis=1, - ) - interval_len = d[1] - d[0] - integrand /= interval_len - - integrand /= X.n_samples + num_functions_above = X.copy( + data_matrix=rankdata( + -X.data_matrix, + method='max', + axis=0, + ) - 1, + ) - return integrand.flatten() + return ( + average_function_value(num_functions_above) + / num_functions_above.n_samples + ).ravel() def depth_based_median( diff --git a/skfda/misc/metrics/_lp_norms.py b/skfda/misc/metrics/_lp_norms.py index e0cd2904f..0465dc005 100644 --- a/skfda/misc/metrics/_lp_norms.py +++ b/skfda/misc/metrics/_lp_norms.py @@ -7,7 +7,7 @@ import scipy.integrate from typing_extensions import Final -from ...representation import FData, FDataBasis +from ...representation import FData, FDataBasis, FDataGrid from ...typing._metric import Norm from ...typing._numpy import NDArrayFloat @@ -135,20 +135,21 @@ def __call__(self, vector: Union[NDArrayFloat, FData]) -> NDArrayFloat: ) res = np.sqrt(integral[0]).flatten() - else: + elif isinstance(vector, FDataGrid): data_matrix = vector.data_matrix - original_shape = data_matrix.shape - data_matrix = data_matrix.reshape(-1, original_shape[-1]) - data_matrix = (np.linalg.norm( - vector.data_matrix, - ord=vector_norm, - axis=-1, - keepdims=True, - ) if isinstance(vector_norm, (float, int)) - else vector_norm(data_matrix) - ) - data_matrix = data_matrix.reshape(original_shape[:-1] + (1,)) + if isinstance(vector_norm, (float, int)): + data_matrix = np.linalg.norm( + vector.data_matrix, + ord=vector_norm, + axis=-1, + keepdims=True, + ) + else: + original_shape = data_matrix.shape + data_matrix = data_matrix.reshape(-1, original_shape[-1]) + data_matrix = vector_norm(data_matrix) + data_matrix = data_matrix.reshape(original_shape[:-1] + (1,)) if np.isinf(self.p): @@ -157,18 +158,19 @@ def __call__(self, vector: Union[NDArrayFloat, FData]) -> NDArrayFloat: axis=tuple(range(1, data_matrix.ndim)), ) - elif vector.dim_domain == 1: + else: + integrand = vector.copy( + data_matrix=data_matrix ** self.p, + coordinate_names=(None,), + ) # Computes the norm, approximating the integral with Simpson's # rule. - res = scipy.integrate.simpson( - data_matrix[..., 0] ** self.p, - x=vector.grid_points, - ) ** (1 / self.p) - - else: - # Needed to perform surface integration - return NotImplemented + res = integrand.integrate().ravel() ** (1 / self.p) + else: + raise NotImplementedError( + f"LpNorm not implemented for type {type(vector)}", + ) if len(res) == 1: return res[0] # type: ignore[no-any-return] diff --git a/skfda/tests/test_metrics.py b/skfda/tests/test_metrics.py index 4e4746901..440806379 100644 --- a/skfda/tests/test_metrics.py +++ b/skfda/tests/test_metrics.py @@ -89,10 +89,11 @@ def test_lp_norm_surface_inf(self) -> None: ) def test_lp_norm_surface(self) -> None: - """Test that integration of surfaces has not been implemented.""" - # Integration of surfaces not implemented, add test case after - # implementation - self.assertEqual(lp_norm(self.fd_surface, p=1), NotImplemented) + """Test the integration of surfaces.""" + np.testing.assert_allclose( + lp_norm(self.fd_surface, p=1), + [0.12566256, 0.12563755, 0.12566133], + ) def test_lp_error_dimensions(self) -> None: """Test error on metric between different kind of objects.""" From 2f43c9121bfd547e83eca04f35af13e05bb62f43 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 10 Oct 2023 11:58:22 +0200 Subject: [PATCH 176/192] Fix Mypy errors in hat matrix. --- skfda/misc/hat_matrix.py | 41 +++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/skfda/misc/hat_matrix.py b/skfda/misc/hat_matrix.py index 6deae4e03..81694bd47 100644 --- a/skfda/misc/hat_matrix.py +++ b/skfda/misc/hat_matrix.py @@ -11,20 +11,21 @@ import abc import math -from typing import Callable, TypeVar, Union, overload +from typing import Callable, Final, TypeVar, Union, overload import numpy as np from .._utils._sklearn_adapter import BaseEstimator from ..representation._functional_data import FData from ..representation.basis import FDataBasis -from ..typing._base import GridPointsLike from ..typing._numpy import NDArrayFloat from . import kernels -Input = TypeVar("Input", bound=Union[FData, GridPointsLike]) +Input = TypeVar("Input", bound=Union[FData, NDArrayFloat]) Prediction = TypeVar("Prediction", bound=Union[NDArrayFloat, FData]) +DEFAULT_BANDWIDTH_PERCENTILE: Final = 15 + class HatMatrix( BaseEstimator, @@ -52,8 +53,8 @@ def __call__( self, *, delta_x: NDArrayFloat, - X_train: Input | None = None, - X: Input | None = None, + X_train: Input, + X: Input, y_train: None = None, weights: NDArrayFloat | None = None, _cv: bool = False, @@ -65,8 +66,8 @@ def __call__( self, *, delta_x: NDArrayFloat, - X_train: Input | None = None, - X: Input | None = None, + X_train: Input, + X: Input, y_train: Prediction | None = None, weights: NDArrayFloat | None = None, _cv: bool = False, @@ -77,8 +78,8 @@ def __call__( self, *, delta_x: NDArrayFloat, - X_train: Input | None = None, - X: Input | None = None, + X_train: Input, + X: Input, y_train: NDArrayFloat | FData | None = None, weights: NDArrayFloat | None = None, _cv: bool = False, @@ -186,7 +187,7 @@ def _hat_matrix_function_not_normalized( ) -> NDArrayFloat: bandwidth = ( - np.percentile(delta_x, 15) + float(np.percentile(delta_x, DEFAULT_BANDWIDTH_PERCENTILE)) if self.bandwidth is None else self.bandwidth ) @@ -278,8 +279,8 @@ def __call__( self, *, delta_x: NDArrayFloat, - X_train: FData | NDArrayFloat | None = None, - X: FData | NDArrayFloat | None = None, + X_train: FData | NDArrayFloat, + X: FData | NDArrayFloat, y_train: None = None, weights: NDArrayFloat | None = None, _cv: bool = False, @@ -291,8 +292,8 @@ def __call__( self, *, delta_x: NDArrayFloat, - X_train: FData | GridPointsLike | None = None, - X: FData | GridPointsLike | None = None, + X_train: FData | NDArrayFloat, + X: FData | NDArrayFloat, y_train: Prediction | None = None, weights: NDArrayFloat | None = None, _cv: bool = False, @@ -303,19 +304,19 @@ def __call__( # noqa: D102 self, *, delta_x: NDArrayFloat, - X_train: FData | GridPointsLike | None = None, - X: FData | GridPointsLike | None = None, + X_train: FData | NDArrayFloat, + X: FData | NDArrayFloat, y_train: NDArrayFloat | FData | None = None, weights: NDArrayFloat | None = None, _cv: bool = False, ) -> NDArrayFloat | FData: bandwidth = ( - np.percentile(delta_x, 15) + float(np.percentile(delta_x, DEFAULT_BANDWIDTH_PERCENTILE)) if self.bandwidth is None else self.bandwidth ) - + # Smoothing for functions of one variable if not isinstance(X_train, FDataBasis) and X_train[0].shape[0] == 1: delta_x = np.subtract.outer( @@ -323,6 +324,8 @@ def __call__( # noqa: D102 X_train.flatten(), ) + assert y_train is None + return super().__call__( # noqa: WPS503 delta_x=delta_x, X_train=X_train, @@ -341,7 +344,7 @@ def __call__( # noqa: D102 and isinstance(X, FDataBasis) ): raise ValueError("Only FDataBasis is supported for now.") - + m1 = X_train.coefficients m2 = X.coefficients From e6ce50550b3857e4d6d11a89f7fe2e0a4c85ac81 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 10 Oct 2023 12:46:41 +0200 Subject: [PATCH 177/192] Add test. --- skfda/preprocessing/smoothing/_linear.py | 1 + skfda/tests/test_smoothing.py | 34 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/skfda/preprocessing/smoothing/_linear.py b/skfda/preprocessing/smoothing/_linear.py index 99368b1cf..09c6fadcc 100644 --- a/skfda/preprocessing/smoothing/_linear.py +++ b/skfda/preprocessing/smoothing/_linear.py @@ -28,6 +28,7 @@ class _LinearSmoother( ``hat_matrix`` to define the smoothing or 'hat' matrix. """ + input_points_: GridPoints output_points_: GridPoints diff --git a/skfda/tests/test_smoothing.py b/skfda/tests/test_smoothing.py index a76825640..315d2b3a5 100644 --- a/skfda/tests/test_smoothing.py +++ b/skfda/tests/test_smoothing.py @@ -4,6 +4,7 @@ import numpy as np import sklearn +from sklearn.datasets import load_digits from typing_extensions import Literal import skfda @@ -173,6 +174,39 @@ def test_knn(self) -> None: np.testing.assert_allclose(hat_matrix, hat_matrix_r) +class TestSurfaceSmoother(unittest.TestCase): + """Test that smoothing works on surfaces.""" + + def test_knn(self) -> None: + """Check 2D smoothing using digits dataset.""" + X, y = load_digits(return_X_y=True) + X = X.reshape(-1, 8, 8) + + fd = skfda.FDataGrid(X) + + ks = KernelSmoother( + kernel_estimator=KNeighborsHatMatrix(n_neighbors=1)) + fd_trans = ks.fit_transform(fd) + + np.testing.assert_allclose(fd_trans.data_matrix, fd.data_matrix) + + ks = KernelSmoother( + kernel_estimator=KNeighborsHatMatrix(n_neighbors=3)) + fd_trans = ks.fit_transform(fd) + + res = [ + [0, 1.25, 7.75, 10.5, 8.25, 6.25, 1.5, 0], + [0, 3.2, 9.6, 10.6, 9.8, 8.4, 5.6, 1.25], + [0.75, 4.4, 9, 6.4, 4.6, 8.4, 6.4, 2], + [1, 4.8, 7.8, 2.8, 1.6, 7.2, 6.4, 2], + [1.25, 4.2, 7.2, 1.6, 2, 7.4, 6.4, 2], + [1, 4.4, 7.4, 3.4, 4.6, 8.2, 5.4, 1.75], + [0.5, 4, 7.6, 8.4, 7.6, 6.8, 3.8, 0], + [0, 2, 8.25, 8.5, 8.25, 5.5, 0, 0], + ] + np.testing.assert_allclose(fd_trans.data_matrix[0, ..., 0], res) + + class TestBasisSmoother(unittest.TestCase): """Test Basis Smoother.""" From 7b0c0b7b134cac3e0cd30ddf2d459dcc8cc06689 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Wed, 11 Oct 2023 12:49:19 +0200 Subject: [PATCH 178/192] Reduce test taking too long. --- skfda/tests/test_recursive_maxima_hunting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/tests/test_recursive_maxima_hunting.py b/skfda/tests/test_recursive_maxima_hunting.py index 9a435b8f9..389e095ac 100644 --- a/skfda/tests/test_recursive_maxima_hunting.py +++ b/skfda/tests/test_recursive_maxima_hunting.py @@ -82,7 +82,7 @@ def test_fit_exponential(self) -> None: join. """ - n_samples = 10000 + n_samples = 1000 n_features = 100 def mean_1( # noqa: WPS430 From 93fdf3d0c7efa6d94cfbdc74b3c45cdfab4fbebe Mon Sep 17 00:00:00 2001 From: pcuestas Date: Thu, 12 Oct 2023 10:15:57 +0200 Subject: [PATCH 179/192] Set `correction=0` by default (and explicitly set the correction value where it was not set, to avoid changing the previous behavior of the modules) --- skfda/exploratory/stats/_stats.py | 8 ++++---- skfda/inference/anova/_anova_oneway.py | 2 ++ skfda/inference/hotelling/_hotelling.py | 2 ++ skfda/misc/covariances.py | 4 ++-- skfda/representation/_functional_data.py | 8 ++++---- skfda/representation/basis/_fdatabasis.py | 12 ++++++------ skfda/representation/grid.py | 16 ++++++++-------- 7 files changed, 28 insertions(+), 24 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index bc40b518c..8d5b2478c 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -43,7 +43,7 @@ def mean( return (X * weight).sum() -def var(X: FData, correction: int = 1) -> FDataGrid: +def var(X: FData, correction: int = 0) -> FDataGrid: """ Compute the variance of a set of samples in a FData object. @@ -51,7 +51,7 @@ def var(X: FData, correction: int = 1) -> FDataGrid: X: Object containing all the set of samples whose variance is desired. correction: degrees of freedom adjustment. The divisor used in the calculation is `N - correction`, where `N` represents the number of - elements. Default: `1`. + elements. Default: `0`. Returns: Variance of all the samples in the original object, as a @@ -78,7 +78,7 @@ def gmean(X: FDataGrid) -> FDataGrid: def cov( X: FData, - correction: int = 1, + correction: int = 0, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: """ Compute the covariance. @@ -90,7 +90,7 @@ def cov( X: Object containing different samples of a functional variable. correction: degrees of freedom adjustment. The divisor used in the calculation is `N - correction`, where `N` represents the number of - elements. Default: `1`. + elements. Default: `0`. Returns: diff --git a/skfda/inference/anova/_anova_oneway.py b/skfda/inference/anova/_anova_oneway.py index feeb18a17..9b4752c20 100644 --- a/skfda/inference/anova/_anova_oneway.py +++ b/skfda/inference/anova/_anova_oneway.py @@ -220,6 +220,7 @@ def _anova_bootstrap( cov_est = concatenate(fd_grouped).cov( grid_points, grid_points, + correction=1, ) k_est = [cov_est] * len(fd_grouped) else: @@ -228,6 +229,7 @@ def _anova_bootstrap( fdg.cov( grid_points, grid_points, + correction=1, ) for fdg in fd_grouped ] diff --git a/skfda/inference/hotelling/_hotelling.py b/skfda/inference/hotelling/_hotelling.py index 9f07e4944..f0aba205e 100644 --- a/skfda/inference/hotelling/_hotelling.py +++ b/skfda/inference/hotelling/_hotelling.py @@ -103,10 +103,12 @@ def hotelling_t2( k1 = fd1.cov( fd1.grid_points[0], fd1.grid_points[0], + correction=1, ) k2 = fd2.cov( fd2.grid_points[0], fd2.grid_points[0], + correction=1, ) m = m.reshape((-1, 1)) # Reshaping the mean for a proper matrix product diff --git a/skfda/misc/covariances.py b/skfda/misc/covariances.py index 05d94f21f..43635eb02 100644 --- a/skfda/misc/covariances.py +++ b/skfda/misc/covariances.py @@ -817,7 +817,7 @@ class EmpiricalGrid(Empirical): cov_fdata: FDataGrid correction: int - def __init__(self, data: FDataGrid, correction: int = 1) -> None: + def __init__(self, data: FDataGrid, correction: int = 0) -> None: super().__init__(data=data) self.correction = correction @@ -853,7 +853,7 @@ class EmpiricalBasis(Empirical): coeff_matrix: NDArrayFloat correction: int - def __init__(self, data: FDataBasis, correction: int = 1) -> None: + def __init__(self, data: FDataBasis, correction: int = 0) -> None: super().__init__(data=data) self.correction = correction diff --git a/skfda/representation/_functional_data.py b/skfda/representation/_functional_data.py index f62e8ccde..ee8813bbc 100644 --- a/skfda/representation/_functional_data.py +++ b/skfda/representation/_functional_data.py @@ -826,7 +826,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, - correction: int = 1, + correction: int = 0, ) -> NDArrayFloat: pass @@ -834,7 +834,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, - correction: int = 1, + correction: int = 0, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -844,7 +844,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, - correction: int = 1, + correction: int = 0, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -866,7 +866,7 @@ def cov( # noqa: WPS320, WPS451 t_points: Points where the covariance function is evaluated. correction: degrees of freedom adjustment. The divisor used in the calculation is `N - correction`, where `N` represents the - number of elements. Default: `1`. + number of elements. Default: `0`. Returns: Covariance function. diff --git a/skfda/representation/basis/_fdatabasis.py b/skfda/representation/basis/_fdatabasis.py index ae4ef0813..945dda05d 100644 --- a/skfda/representation/basis/_fdatabasis.py +++ b/skfda/representation/basis/_fdatabasis.py @@ -445,7 +445,7 @@ def sum( # noqa: WPS125 def var( self: T, eval_points: Optional[NDArrayFloat] = None, - correction: int = 1, + correction: int = 0, ) -> T: """Compute the variance of the functional data object. @@ -462,7 +462,7 @@ def var( between 501 and 10 times the number of basis. correction: degrees of freedom adjustment. The divisor used in the calculation is `N - correction`, where `N` represents the - number of elements. Default: `1`. + number of elements. Default: `0`. Returns: Variance of the original object. @@ -478,7 +478,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, - correction: int = 1, + correction: int = 0, ) -> NDArrayFloat: pass @@ -486,7 +486,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, - correction: int = 1, + correction: int = 0, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -495,7 +495,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, - correction: int = 1, + correction: int = 0, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -519,7 +519,7 @@ def cov( # noqa: WPS320, WPS451 t_points: Points where the covariance function is evaluated. correction: degrees of freedom adjustment. The divisor used in the calculation is `N - correction`, where `N` represents the - number of elements. Default: `1`. + number of elements. Default: `0`. Returns: Covariance function. diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 9aae66ac8..bdd01c3e4 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -582,13 +582,13 @@ def sum( # noqa: WPS125 sample_names=(None,), ) - def var(self: T, correction: int = 1) -> T: + def var(self: T, correction: int = 0) -> T: """Compute the variance of a set of samples in a FDataGrid object. Args: - correction: "Delta Degrees of Freedom": the divisor used in the - calculation is `N - correction`, where `N` represents the number of - elements. By default `correction` is 1. + correction: degrees of freedom adjustment. The divisor used in the + calculation is `N - correction`, where `N` represents the + number of elements. Default: `0`. Returns: A FDataGrid object with just one sample representing the @@ -610,7 +610,7 @@ def cov( # noqa: WPS451 s_points: NDArrayFloat, t_points: NDArrayFloat, /, - correction: int = 1, + correction: int = 0, ) -> NDArrayFloat: pass @@ -618,7 +618,7 @@ def cov( # noqa: WPS451 def cov( # noqa: WPS451 self: T, /, - correction: int = 1, + correction: int = 0, ) -> Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat]: pass @@ -627,7 +627,7 @@ def cov( # noqa: WPS320, WPS451 s_points: Optional[NDArrayFloat] = None, t_points: Optional[NDArrayFloat] = None, /, - correction: int = 1, + correction: int = 0, ) -> Union[ Callable[[NDArrayFloat, NDArrayFloat], NDArrayFloat], NDArrayFloat, @@ -645,7 +645,7 @@ def cov( # noqa: WPS320, WPS451 t_points: Grid points where the covariance function is evaluated. correction: degrees of freedom adjustment. The divisor used in the calculation is `N - correction`, where `N` represents the - number of elements. Default: `1`. + number of elements. Default: `0`. Returns: Covariance function. From 6b5e47d152bda6d5f33a0aebc5ff8962cadc26b6 Mon Sep 17 00:00:00 2001 From: pcuestas Date: Thu, 12 Oct 2023 11:27:47 +0200 Subject: [PATCH 180/192] sum instead of diag to compute std in std_fdatagrid integrand --- skfda/exploratory/stats/_stats.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index af0bb1da0..903261590 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -146,7 +146,10 @@ def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: def std_function(t_points: NDArrayFloat) -> NDArrayFloat: # noqa: WPS430 basis_evaluation = basis(t_points).reshape((basis.n_basis, -1)) std_values = np.sqrt( - np.diag(basis_evaluation.T @ coeff_cov_matrix @ basis_evaluation), + np.sum( + basis_evaluation * (coeff_cov_matrix @ basis_evaluation), + axis=0, + ), ) return np.reshape(std_values, (1, -1, X.dim_codomain)) From bb5923d6138ecb1ae1dc0fe506324d88f548d3cd Mon Sep 17 00:00:00 2001 From: pcuestas Date: Thu, 12 Oct 2023 11:30:45 +0200 Subject: [PATCH 181/192] Rename ddof -> correction --- skfda/exploratory/stats/_stats.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/skfda/exploratory/stats/_stats.py b/skfda/exploratory/stats/_stats.py index 009261497..5bf81b3b9 100644 --- a/skfda/exploratory/stats/_stats.py +++ b/skfda/exploratory/stats/_stats.py @@ -103,20 +103,20 @@ def cov( @functools.singledispatch -def std(X: F, ddof: int = 1) -> F: +def std(X: F, correction: int = 1) -> F: r""" Compute the standard deviation of all the samples in a FData object. .. math:: - \text{std}_X(t) = \sqrt{\frac{1}{N-\text{ddof}} + \text{std}_X(t) = \sqrt{\frac{1}{N-\text{correction}} \sum_{n=1}^{N}{\left(X_n(t) - \overline{X}(t)\right)^2}} Args: X: Object containing all the samples whose standard deviation is wanted. - ddof: Means "Delta Degrees of Freedom". The divisor used in - calculations is `N - ddof`, where `N` represents the number of - samples in `X`. By default ddof is 1. + correction: degrees of freedom adjustment. The divisor used in the + calculation is `N - correction`, where `N` represents the number of + elements. Default: `0`. Returns: Standard deviation of all the samples in the original object, as a @@ -127,22 +127,24 @@ def std(X: F, ddof: int = 1) -> F: @std.register -def std_fdatagrid(X: FDataGrid, ddof: int = 1) -> FDataGrid: +def std_fdatagrid(X: FDataGrid, correction: int = 1) -> FDataGrid: """Compute the standard deviation of a FDataGrid.""" return X.copy( - data_matrix=np.std(X.data_matrix, axis=0, ddof=ddof)[np.newaxis, ...], + data_matrix=np.std( + X.data_matrix, axis=0, ddof=correction, + )[np.newaxis, ...], sample_names=(None,), ) @std.register -def std_fdatabasis(X: FDataBasis, ddof: int = 1) -> FDataBasis: +def std_fdatabasis(X: FDataBasis, correction: int = 1) -> FDataBasis: """Compute the standard deviation of a FDataBasis.""" from ..._utils import function_to_fdatabasis basis = X.basis coeff_cov_matrix = np.cov( - X.coefficients, rowvar=False, ddof=ddof, + X.coefficients, rowvar=False, ddof=correction, ).reshape((basis.n_basis, basis.n_basis)) def std_function(t_points: NDArrayFloat) -> NDArrayFloat: # noqa: WPS430 From febfe36fbb5c2f947c4956eebfe6d4ce08768763 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Thu, 12 Oct 2023 14:18:17 +0200 Subject: [PATCH 182/192] Fix some warnings. --- skfda/datasets/_real_datasets.py | 5 ++--- skfda/exploratory/visualization/_boxplot.py | 5 ++--- skfda/exploratory/visualization/_magnitude_shape_plot.py | 3 +-- skfda/ml/classification/_depth_classifiers.py | 6 +++--- .../dim_reduction/feature_extraction/__init__.py | 1 + skfda/preprocessing/dim_reduction/projection/__init__.py | 1 + skfda/preprocessing/smoothing/kernel_smoothers.py | 1 + skfda/representation/basis/_bspline_basis.py | 3 ++- skfda/representation/basis/_constant_basis.py | 1 + skfda/representation/basis/_finite_element_basis.py | 1 + skfda/representation/basis/_fourier_basis.py | 1 + skfda/representation/basis/_monomial_basis.py | 1 + skfda/representation/basis/_tensor_basis.py | 1 + skfda/representation/basis/_vector_basis.py | 1 + 14 files changed, 19 insertions(+), 12 deletions(-) diff --git a/skfda/datasets/_real_datasets.py b/skfda/datasets/_real_datasets.py index 28415c76c..d345ebdcf 100644 --- a/skfda/datasets/_real_datasets.py +++ b/skfda/datasets/_real_datasets.py @@ -5,12 +5,11 @@ import numpy as np import pandas as pd +import rdata from pandas import DataFrame, Series from sklearn.utils import Bunch from typing_extensions import Literal -import rdata - from ..representation import FDataGrid from ..typing._numpy import NDArrayFloat, NDArrayInt @@ -180,7 +179,7 @@ def fetch_ucr( The UCR/UEA Time Series Classification repository, hosted at www.timeseriesclassification.com includes plenty of classification problems with univariate and multivariate time series - \ :footcite:p:`dau++_2019_ucr,bagnall++_2018_uea`. + :footcite:p:`dau++_2019_ucr,bagnall++_2018_uea`. They are widely used in the functional data classification literature. Args: diff --git a/skfda/exploratory/visualization/_boxplot.py b/skfda/exploratory/visualization/_boxplot.py index 19aae239b..e315a92f0 100644 --- a/skfda/exploratory/visualization/_boxplot.py +++ b/skfda/exploratory/visualization/_boxplot.py @@ -11,7 +11,6 @@ from typing import Sequence, Tuple import matplotlib -import matplotlib.pyplot as plt import numpy as np from matplotlib.axes import Axes from matplotlib.colors import Colormap @@ -376,7 +375,7 @@ def __init__( self._fdatagrid = fdatagrid self._prob = prob - self._colormap = plt.cm.get_cmap('RdPu') + self._colormap = matplotlib.colormaps['RdPu'] self.barcol = "blue" self.outliercol = "red" self.mediancol = "black" @@ -701,7 +700,7 @@ def __init__( self._non_outlying_envelope = _envelopes.compute_envelope(inliers) self._fdatagrid = fdatagrid - self.colormap = plt.cm.get_cmap('Greys') + self.colormap = matplotlib.colormaps['Greys'] self._boxcol = 1.0 self._outcol = 0.7 diff --git a/skfda/exploratory/visualization/_magnitude_shape_plot.py b/skfda/exploratory/visualization/_magnitude_shape_plot.py index bb5c56d51..6fc13fbdf 100644 --- a/skfda/exploratory/visualization/_magnitude_shape_plot.py +++ b/skfda/exploratory/visualization/_magnitude_shape_plot.py @@ -10,7 +10,6 @@ from typing import Any, Sequence import matplotlib -import matplotlib.pyplot as plt import numpy as np from matplotlib.artist import Artist from matplotlib.axes import Axes @@ -193,7 +192,7 @@ def __init__( self._fdata = fdata self._outliers = outliers - self._colormap = plt.cm.get_cmap('seismic') + self._colormap = matplotlib.colormaps['seismic'] self._color = 0.2 self._outliercol = 0.8 self.xlabel = 'MO' diff --git a/skfda/ml/classification/_depth_classifiers.py b/skfda/ml/classification/_depth_classifiers.py index 47e29e239..c258d608b 100644 --- a/skfda/ml/classification/_depth_classifiers.py +++ b/skfda/ml/classification/_depth_classifiers.py @@ -71,7 +71,7 @@ class DDClassifier( Depth-versus-depth (DD) classifer for functional data. Transforms the data into a DD-plot and then classifies using a polynomial - of a chosen degree\ :footcite:p:`li++_2012_ddclassifier`. + of a chosen degree :footcite:p:`li++_2012_ddclassifier`. The polynomial passes through zero and maximizes the accuracy of the classification on the train dataset. @@ -117,7 +117,7 @@ class DDClassifier( See also: :class:`~skfda.ml.classification.DDGClassifier` :class:`~skfda.ml.classification.MaximumDepthClassifier` - :class:`~skfda.preprocessing.dim_reduction.feature_extraction._ddg_transformer` + :class:`~skfda.preprocessing.dim_reduction.feature_construction._ddg_transformer` References: .. footbibliography:: @@ -307,7 +307,7 @@ class DDGClassifier( See also: :class:`~skfda.ml.classification.DDClassifier` :class:`~skfda.ml.classification.MaximumDepthClassifier` - :class:`~skfda.preprocessing.dim_reduction.feature_extraction._ddg_transformer` + :class:`~skfda.preprocessing.dim_reduction.feature_construction._ddg_transformer` References: .. footbibliography:: diff --git a/skfda/preprocessing/dim_reduction/feature_extraction/__init__.py b/skfda/preprocessing/dim_reduction/feature_extraction/__init__.py index 70fdefb97..d55d51315 100644 --- a/skfda/preprocessing/dim_reduction/feature_extraction/__init__.py +++ b/skfda/preprocessing/dim_reduction/feature_extraction/__init__.py @@ -8,4 +8,5 @@ 'Please use "dim_reduction" for FPCA' 'or "feature_construction" for feature construction techniques', category=DeprecationWarning, + stacklevel=2, ) diff --git a/skfda/preprocessing/dim_reduction/projection/__init__.py b/skfda/preprocessing/dim_reduction/projection/__init__.py index f028b2627..a56b0ff83 100644 --- a/skfda/preprocessing/dim_reduction/projection/__init__.py +++ b/skfda/preprocessing/dim_reduction/projection/__init__.py @@ -5,4 +5,5 @@ warnings.warn( 'The module "projection" is deprecated. Please use "dim_reduction"', category=DeprecationWarning, + stacklevel=2, ) diff --git a/skfda/preprocessing/smoothing/kernel_smoothers.py b/skfda/preprocessing/smoothing/kernel_smoothers.py index ae3b7ac85..fbe9d68ad 100644 --- a/skfda/preprocessing/smoothing/kernel_smoothers.py +++ b/skfda/preprocessing/smoothing/kernel_smoothers.py @@ -18,6 +18,7 @@ "The \"kernel_smoothers\" module is deprecated. " "Use the \"KernelSmoother\" class instead", DeprecationWarning, + stacklevel=2, ) diff --git a/skfda/representation/basis/_bspline_basis.py b/skfda/representation/basis/_bspline_basis.py index 15059e49c..3c3981db4 100644 --- a/skfda/representation/basis/_bspline_basis.py +++ b/skfda/representation/basis/_bspline_basis.py @@ -460,6 +460,7 @@ def __init__( # noqa: WPS238 knots=knots, ) warnings.warn( - "The BSplines class is deprecated. Use BSplineBasis instead.", + "The BSpline class is deprecated. Use BSplineBasis instead.", DeprecationWarning, + stacklevel=2, ) diff --git a/skfda/representation/basis/_constant_basis.py b/skfda/representation/basis/_constant_basis.py index 6d7d6e24b..3305ba21a 100644 --- a/skfda/representation/basis/_constant_basis.py +++ b/skfda/representation/basis/_constant_basis.py @@ -81,5 +81,6 @@ def __init__(self, domain_range: Optional[DomainRangeLike] = None) -> None: warnings.warn( "The Constant class is deprecated. Use ConstantBasis instead.", DeprecationWarning, + stacklevel=2, ) super().__init__(domain_range=domain_range) diff --git a/skfda/representation/basis/_finite_element_basis.py b/skfda/representation/basis/_finite_element_basis.py index a2a64b0d1..6f8142b68 100644 --- a/skfda/representation/basis/_finite_element_basis.py +++ b/skfda/representation/basis/_finite_element_basis.py @@ -229,4 +229,5 @@ def __init__( "The FiniteElement class is deprecated. Use " "FiniteElementBasis instead.", DeprecationWarning, + stacklevel=2, ) diff --git a/skfda/representation/basis/_fourier_basis.py b/skfda/representation/basis/_fourier_basis.py index c8d324330..cb7b737bd 100644 --- a/skfda/representation/basis/_fourier_basis.py +++ b/skfda/representation/basis/_fourier_basis.py @@ -319,6 +319,7 @@ def __init__( warnings.warn( "The Fourier class is deprecated. Use FourierBasis instead.", DeprecationWarning, + stacklevel=2, ) super().__init__( domain_range=domain_range, diff --git a/skfda/representation/basis/_monomial_basis.py b/skfda/representation/basis/_monomial_basis.py index be6370398..35129ac3a 100644 --- a/skfda/representation/basis/_monomial_basis.py +++ b/skfda/representation/basis/_monomial_basis.py @@ -211,4 +211,5 @@ def __init__( warnings.warn( "The Monomial class is deprecated. Use MonomialBasis instead.", DeprecationWarning, + stacklevel=2, ) diff --git a/skfda/representation/basis/_tensor_basis.py b/skfda/representation/basis/_tensor_basis.py index f4f83dd2d..833395c2f 100644 --- a/skfda/representation/basis/_tensor_basis.py +++ b/skfda/representation/basis/_tensor_basis.py @@ -180,4 +180,5 @@ def __init__(self, basis_list: Iterable[Basis]): warnings.warn( "The Tensor class is deprecated. Use TensorBasis instead.", DeprecationWarning, + stacklevel=2, ) diff --git a/skfda/representation/basis/_vector_basis.py b/skfda/representation/basis/_vector_basis.py index 8c02af61b..ea5be4bb5 100644 --- a/skfda/representation/basis/_vector_basis.py +++ b/skfda/representation/basis/_vector_basis.py @@ -245,4 +245,5 @@ def __init__(self, basis_list: Iterable[Basis]) -> None: "The VectorValued class is deprecated. " "Use VectorValuedBasis instead.", DeprecationWarning, + stacklevel=2, ) From adb1cd8a8b179a01c4ec8a375a4acfdfae81c89b Mon Sep 17 00:00:00 2001 From: VNMabus Date: Fri, 13 Oct 2023 10:16:31 +0200 Subject: [PATCH 183/192] Small fix in doc. --- skfda/preprocessing/missing/_interpolate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/preprocessing/missing/_interpolate.py b/skfda/preprocessing/missing/_interpolate.py index d09ea07b2..d97dff0d8 100644 --- a/skfda/preprocessing/missing/_interpolate.py +++ b/skfda/preprocessing/missing/_interpolate.py @@ -134,7 +134,7 @@ class MissingValuesInterpolation( For multivariate functions, such as surfaces all dimensions are considered. This is currently done using - :external:class:`~scipy.interpolation.LinearNDInterpolator`, which + :external:class:`~scipy.interpolate.LinearNDInterpolator`, which triangulates the space and performs linear barycentric interpolation: >>> X = FDataGrid( From 23d657102e47e1afd7b9ec42014f804f8b08dc79 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Fri, 13 Oct 2023 10:21:28 +0200 Subject: [PATCH 184/192] Update version. --- skfda/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skfda/__init__.py b/skfda/__init__.py index f9434fa97..47a67be43 100644 --- a/skfda/__init__.py +++ b/skfda/__init__.py @@ -30,4 +30,4 @@ concatenate as concatenate, ) -__version__ = "0.9.dev1" +__version__ = "0.9" From 8789db072bf5dbb1b971454574ef1807b2584396 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Fri, 13 Oct 2023 11:23:54 +0200 Subject: [PATCH 185/192] Update version. --- docs/_static/switcher.json | 2 +- skfda/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_static/switcher.json b/docs/_static/switcher.json index 71b626e80..f4e362095 100644 --- a/docs/_static/switcher.json +++ b/docs/_static/switcher.json @@ -5,7 +5,7 @@ "url": "https://fda.readthedocs.io/en/latest/" }, { - "name": "0.8.1 (stable)", + "name": "0.9 (stable)", "version": "stable", "url": "https://fda.readthedocs.io/en/stable/", "preferred": true diff --git a/skfda/__init__.py b/skfda/__init__.py index 47a67be43..f9434fa97 100644 --- a/skfda/__init__.py +++ b/skfda/__init__.py @@ -30,4 +30,4 @@ concatenate as concatenate, ) -__version__ = "0.9" +__version__ = "0.9.dev1" From 02bee403f1c1111284b24b9ed6654460f796ca5b Mon Sep 17 00:00:00 2001 From: VNMabus Date: Fri, 13 Oct 2023 11:47:57 +0200 Subject: [PATCH 186/192] Remove Python 3.8 support. --- .github/workflows/tests.yml | 2 +- pyproject.toml | 4 ++-- skfda/__init__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f08ec8d3f..a3cfcd644 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.8', '3.9'] + python-version: ['3.9', '3.10'] steps: - uses: actions/checkout@v2 diff --git a/pyproject.toml b/pyproject.toml index dc5ea6708..97247f3b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "scikit-fda" description = "Functional Data Analysis Python package." readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" license = {file = "LICENSE.txt"} keywords = [ "functional data", @@ -20,7 +20,7 @@ classifiers = [ "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", diff --git a/skfda/__init__.py b/skfda/__init__.py index f9434fa97..ac12bc7f5 100644 --- a/skfda/__init__.py +++ b/skfda/__init__.py @@ -30,4 +30,4 @@ concatenate as concatenate, ) -__version__ = "0.9.dev1" +__version__ = "0.9.1.dev1" From 4ebf2b65e02efd2192c06d5dfa9c4bbc8c4d3e92 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Fri, 13 Oct 2023 12:00:01 +0200 Subject: [PATCH 187/192] Update readthedocs config. --- readthedocs.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/readthedocs.yml b/readthedocs.yml index 2c148156d..47b68f8d7 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -5,6 +5,11 @@ # Required version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.9" + # Build documentation in the docs/ directory with Sphinx sphinx: builder: html @@ -18,7 +23,6 @@ sphinx: # Optionally set the version of Python and requirements required to build your docs python: - version: "3.8" install: - requirements: readthedocs-requirements.txt - method: pip From d9228519dedc5750b2020fd465309bc1b28baab8 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Fri, 13 Oct 2023 13:22:19 +0200 Subject: [PATCH 188/192] Remove useless requirement files. Optional dependences for building the docs live now in pyproject.toml. --- binder/requirements.txt | 2 +- pyproject.toml | 15 +++++++++++++++ readthedocs-requirements.txt | 14 -------------- readthedocs.yml | 5 +++-- requirements.txt | 7 ------- 5 files changed, 19 insertions(+), 24 deletions(-) delete mode 100644 readthedocs-requirements.txt delete mode 100644 requirements.txt diff --git a/binder/requirements.txt b/binder/requirements.txt index b913cb3e4..9ee80ec8d 100644 --- a/binder/requirements.txt +++ b/binder/requirements.txt @@ -1,4 +1,4 @@ --r ../readthedocs-requirements.txt +scikit-fda[docs] jupytext sphinx-gallery<=0.7.0 . \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 97247f3b5..edf05d3ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,21 @@ dependencies = [ ] [project.optional-dependencies] +docs = [ + "basemap", + "basemap-data", + "basemap-data-hires", + "ipykernel", + "jupyter-sphinx", + "myst-parser", + "pillow", + "pydata-sphinx-theme", + "pytest", + "setuptools>=41.2", + "sphinx>=3", + "sphinx-gallery", + "sphinxcontrib-bibtex", +] test = [ "pytest", "pytest-env", diff --git a/readthedocs-requirements.txt b/readthedocs-requirements.txt deleted file mode 100644 index f646304da..000000000 --- a/readthedocs-requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ --r ./requirements.txt -basemap -basemap-data -basemap-data-hires -ipykernel -Sphinx>=3 -pydata-sphinx-theme -sphinx-gallery -pillow -setuptools>=41.2 -jupyter-sphinx -myst-parser -pytest -sphinxcontrib-bibtex \ No newline at end of file diff --git a/readthedocs.yml b/readthedocs.yml index 47b68f8d7..46689c30d 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -24,6 +24,7 @@ sphinx: # Optionally set the version of Python and requirements required to build your docs python: install: - - requirements: readthedocs-requirements.txt - method: pip - path: . \ No newline at end of file + path: . + extra_requirements: + - docs \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2a806336a..000000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -matplotlib -numpy -scipy -setuptools -scikit-learn -multimethod>=1.2 -findiff From 4f0bd4c09b224ab8ce1f7fe3f760add14be0ba5d Mon Sep 17 00:00:00 2001 From: VNMabus Date: Fri, 13 Oct 2023 20:55:16 +0200 Subject: [PATCH 189/192] Remove some warnings in tests. --- skfda/inference/anova/_anova_oneway.py | 8 ++- skfda/misc/metrics/_utils.py | 4 +- .../ml/classification/_logistic_regression.py | 4 +- skfda/ml/clustering/_hierarchical.py | 2 +- .../dim_reduction/variable_selection/_rkvs.py | 2 +- .../dim_reduction/variable_selection/mrmr.py | 2 +- .../recursive_maxima_hunting.py | 49 ++----------------- 7 files changed, 16 insertions(+), 55 deletions(-) diff --git a/skfda/inference/anova/_anova_oneway.py b/skfda/inference/anova/_anova_oneway.py index 9b4752c20..d95430b71 100644 --- a/skfda/inference/anova/_anova_oneway.py +++ b/skfda/inference/anova/_anova_oneway.py @@ -182,7 +182,13 @@ def v_asymptotic_stat( .. footbibliography:: """ - return float(_v_asymptotic_stat_with_reps(*fd, weights=weights, p=p)) + return float( + _v_asymptotic_stat_with_reps( + *fd, + weights=weights, + p=p, + ).reshape(()) + ) def _anova_bootstrap( diff --git a/skfda/misc/metrics/_utils.py b/skfda/misc/metrics/_utils.py index 9850057a0..30d1fae7e 100644 --- a/skfda/misc/metrics/_utils.py +++ b/skfda/misc/metrics/_utils.py @@ -120,8 +120,8 @@ class NormInducedMetric(Metric[VectorType]): >>> l2_distance = NormInducedMetric(l2_norm) >>> d = l2_distance(fd, fd2) - >>> float('%.3f'% d) - 0.289 + >>> float(d[0]) + 0.288... """ diff --git a/skfda/ml/classification/_logistic_regression.py b/skfda/ml/classification/_logistic_regression.py index ae95a1380..cd0fe6093 100644 --- a/skfda/ml/classification/_logistic_regression.py +++ b/skfda/ml/classification/_logistic_regression.py @@ -132,11 +132,9 @@ def fit( # noqa: D102, WPS210 (self.max_features, n_features), ) - penalty = 'none' if self.penalty is None else self.penalty - # multivariate logistic regression mvlr = mvLogisticRegression( - penalty=penalty, + penalty=self.penalty, C=self.C, solver=self.solver, max_iter=self.max_iter, diff --git a/skfda/ml/clustering/_hierarchical.py b/skfda/ml/clustering/_hierarchical.py index c8f63cf81..75e06e8f3 100644 --- a/skfda/ml/clustering/_hierarchical.py +++ b/skfda/ml/clustering/_hierarchical.py @@ -173,7 +173,7 @@ def _init_estimator(self) -> None: self._estimator = sklearn.cluster.AgglomerativeClustering( n_clusters=self.n_clusters, - affinity='precomputed', + metric='precomputed', memory=self.memory, connectivity=self.connectivity, compute_full_tree=self.compute_full_tree, diff --git a/skfda/preprocessing/dim_reduction/variable_selection/_rkvs.py b/skfda/preprocessing/dim_reduction/variable_selection/_rkvs.py index 4da5810b7..298a68fea 100644 --- a/skfda/preprocessing/dim_reduction/variable_selection/_rkvs.py +++ b/skfda/preprocessing/dim_reduction/variable_selection/_rkvs.py @@ -83,7 +83,7 @@ def _rkhs_vs( [indexes[j]], ]) - new_means = np.atleast_2d(means[new_selection]) + new_means = means[new_selection] lstsq_solution = linalg.lstsq( variances[new_selection[:, np.newaxis], new_selection], diff --git a/skfda/preprocessing/dim_reduction/variable_selection/mrmr.py b/skfda/preprocessing/dim_reduction/variable_selection/mrmr.py index 638d93bf3..036663ccf 100644 --- a/skfda/preprocessing/dim_reduction/variable_selection/mrmr.py +++ b/skfda/preprocessing/dim_reduction/variable_selection/mrmr.py @@ -160,7 +160,7 @@ def _mrmr( redundancies[last_selected, j] = redundancy_dependence_measure( X[:, last_selected, np.newaxis], X[:, j, np.newaxis], - ) + ).item() redundancies[j, last_selected] = redundancies[last_selected, j] W = np.mean( diff --git a/skfda/preprocessing/dim_reduction/variable_selection/recursive_maxima_hunting.py b/skfda/preprocessing/dim_reduction/variable_selection/recursive_maxima_hunting.py index ca7b377b8..432b65bb8 100644 --- a/skfda/preprocessing/dim_reduction/variable_selection/recursive_maxima_hunting.py +++ b/skfda/preprocessing/dim_reduction/variable_selection/recursive_maxima_hunting.py @@ -17,6 +17,7 @@ overload, ) +import dcor import numpy as np import numpy.linalg as linalg import numpy.ma as ma @@ -25,7 +26,6 @@ from sklearn.base import clone from typing_extensions import Literal -import dcor from skfda.exploratory.stats.covariance import ( CovarianceEstimator, EmpiricalCovariance, @@ -41,7 +41,6 @@ if TYPE_CHECKING: from ....misc.covariances import CovarianceLike - import GPy def _transform_to_2d(t: ArrayLike) -> NDArrayFloat: @@ -56,48 +55,6 @@ def _transform_to_2d(t: ArrayLike) -> NDArrayFloat: return t -class _PicklableKernel(): - """Class used to pickle GPy kernels.""" - - def __init__(self, kernel: GPy.kern.Kern) -> None: - super().__setattr__('_PicklableKernel__kernel', kernel) - - def __getattr__(self, name: str) -> Any: - if name != '__deepcopy__': - return getattr(self.__kernel, name) - - def __setattr__(self, name: str, value: Any) -> None: - setattr(self.__kernel, name, value) - - def __getstate__(self) -> Mapping[str, Any]: - return { - 'class': self.__kernel.__class__, - 'input_dim': self.__kernel.input_dim, - 'values': self.__kernel.param_array, - } - - def __setstate__(self, state: Mapping[str, Any]) -> None: - super().__setattr__('_PicklableKernel__kernel', state['class']( - input_dim=state['input_dim']), - ) - self.__kernel.param_array[...] = state['values'] - - def __call__(self, *args: Any, **kwargs: Any) -> NDArrayFloat: - return self.__kernel.K(*args, **kwargs) # type: ignore[no-any-return] - - -def make_kernel(k: CovarianceLike) -> CovarianceLike: - try: - import GPy - except ImportError: - return k - - if isinstance(k, GPy.kern.Kern): - return _PicklableKernel(k) - - return k - - def _absolute_argmax( function: FDataGrid, *, @@ -255,7 +212,7 @@ def __init__( super().__init__() self.mean = mean - self.cov = make_kernel(cov) + self.cov = cov def _evaluate_mean(self, t: NDArrayFloat) -> NDArrayFloat: @@ -625,7 +582,7 @@ def __call__( **kwargs: Any, ) -> bool: - score = float(dependences.data_matrix[0, selected_index, 0]) + score = float(dependences.data_matrix[(0,) + selected_index + (0,)]) return score < self.threshold From 9f74e2436ba12789d80e3904346016a0fb14e122 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Fri, 13 Oct 2023 23:13:16 +0200 Subject: [PATCH 190/192] Some tests are now faster. --- skfda/ml/classification/_logistic_regression.py | 6 +++--- skfda/tests/test_recursive_maxima_hunting.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/skfda/ml/classification/_logistic_regression.py b/skfda/ml/classification/_logistic_regression.py index cd0fe6093..37ff0e4f6 100644 --- a/skfda/ml/classification/_logistic_regression.py +++ b/skfda/ml/classification/_logistic_regression.py @@ -62,8 +62,8 @@ class LogisticRegression( >>> from skfda.datasets import make_gaussian_process >>> from skfda.ml.classification import LogisticRegression >>> - >>> n_samples = 10000 - >>> n_features = 200 + >>> n_samples = 2000 + >>> n_features = 101 >>> >>> def mean_1(t): ... return (np.abs(t - 0.25) @@ -90,7 +90,7 @@ class LogisticRegression( >>> np.allclose(sorted(lr.points_), [0.25, 0.5, 0.75], rtol=1e-2) True >>> lr.score(X[1::2],y[1::2]) - 0.7498 + 0.768 References: .. footbibliography:: diff --git a/skfda/tests/test_recursive_maxima_hunting.py b/skfda/tests/test_recursive_maxima_hunting.py index 389e095ac..d781a69b9 100644 --- a/skfda/tests/test_recursive_maxima_hunting.py +++ b/skfda/tests/test_recursive_maxima_hunting.py @@ -25,8 +25,8 @@ def test_gaussian_homoscedastic(self) -> None: join. """ - n_samples = 10000 - n_features = 100 + n_samples = 1000 + n_features = 101 def mean_1( # noqa: WPS430 t: np.typing.NDArray[np.float_], @@ -66,7 +66,7 @@ def mean_1( # noqa: WPS430 rmh.fit(X, y) point_mask = rmh.get_support() points = X.grid_points[0][point_mask] - np.testing.assert_allclose(points, [0.25, 0.5, 0.75], rtol=1e-1) + np.testing.assert_allclose(points, [0.25, 0.5, 0.75], rtol=1e-2) @pytest.mark.filterwarnings( 'ignore::sklearn.exceptions.ConvergenceWarning' @@ -83,7 +83,7 @@ def test_fit_exponential(self) -> None: """ n_samples = 1000 - n_features = 100 + n_features = 101 def mean_1( # noqa: WPS430 t: np.typing.NDArray[np.float_], @@ -129,7 +129,7 @@ def mean_1( # noqa: WPS430 rmh.fit(X, y) point_mask = rmh.get_support() points = X.grid_points[0][point_mask] - np.testing.assert_allclose(points, [0.25, 0.5, 0.75], rtol=1e-1) + np.testing.assert_allclose(points, [0.25, 0.5, 0.75], rtol=1e-2) if __name__ == '__main__': From 3e7495205f8a2e2c959a132c4612c9e83e18cb88 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Sun, 15 Oct 2023 03:04:56 +0200 Subject: [PATCH 191/192] Change example from Basemap to Cartopy. Closes #514. --- .../full_examples/plot_aemet_unsupervised.py | 81 ++++++++----------- pyproject.toml | 4 +- 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/examples/full_examples/plot_aemet_unsupervised.py b/examples/full_examples/plot_aemet_unsupervised.py index 8f8886c30..ece1b0829 100644 --- a/examples/full_examples/plot_aemet_unsupervised.py +++ b/examples/full_examples/plot_aemet_unsupervised.py @@ -14,11 +14,13 @@ from typing import Any, Mapping, Tuple +import cartopy.crs as ccrs import matplotlib.pyplot as plt import numpy as np import sklearn.cluster +from cartopy.io.img_tiles import GoogleTiles from matplotlib.axes import Axes -from mpl_toolkits.basemap import Basemap +from matplotlib.figure import Figure from skfda.datasets import fetch_aemet from skfda.exploratory.depth import ModifiedBandDepth @@ -68,8 +70,8 @@ ############################################################################## # We want to plot the cluster of each station in the map of Spain. We need to # define first auxiliary variables and functions for plotting. -coords_spain = (-10, 34.98, 5, 44.8) -coords_canary = (-18.5, 27.5, -13, 29.5) +coords_spain = (-10, 5, 34.98, 44.8) +coords_canary = (-18.5, -13, 27.5, 29.5) # It is easier to obtain the longitudes and latitudes from the data in # a Pandas dataframe. @@ -81,24 +83,20 @@ def create_map( coords: Tuple[float, float, float, float], - ax: Axes, -) -> Basemap: + figsize: Tuple[float, float], +) -> Figure: """Create a map for a region of the world.""" - basemap = Basemap( - *coords, - projection='merc', - resolution="h", - epsg=4326, - ax=ax, - fix_aspect=False, - ) - basemap.arcgisimage( - service='World_Imagery', - xpixels=1000, - dpi=100, - ) + tiler = GoogleTiles(style="satellite") + mercator = tiler.crs - return basemap + fig = plt.figure(figsize=figsize) + ax = fig.add_axes([0, 0, 1, 1], projection=mercator) + ax.set_extent(coords, crs=ccrs.PlateCarree()) + + ax.add_image(tiler, 8) + ax.set_adjustable('datalim') + + return fig def plot_cluster_points( @@ -106,19 +104,18 @@ def plot_cluster_points( latitudes: np.typing.NDArray[np.floating[Any]], clusters: np.typing.NDArray[np.integer[Any]], color_map: Mapping[int, str], - basemap: Basemap, ax: Axes, ) -> None: """Plot the stations in a map with their cluster color.""" - x, y = basemap(longitudes, latitudes) for cluster in range(n_clusters): selection = (clusters == cluster) ax.scatter( - x[selection], - y[selection], + longitudes[selection], + latitudes[selection], s=64, color=color_map[cluster], edgecolors='white', + transform=ccrs.Geodetic(), ) @@ -144,29 +141,23 @@ def plot_cluster_points( # We now plot the obtained clustering in the maps. # Mainland -fig_spain = plt.figure(figsize=(8, 6)) -ax_spain = fig_spain.add_axes([0, 0, 1, 1]) -map_spain = create_map(coords_spain, ax=ax_spain) +fig_spain = create_map(coords_spain, figsize=(8, 6)) plot_cluster_points( longitudes=station_longitudes, latitudes=station_latitudes, clusters=fda_clusters, color_map=fda_color_map, - basemap=map_spain, - ax=ax_spain, + ax=fig_spain.axes[0], ) # Canary Islands -fig_canary = plt.figure(figsize=(8, 3)) -ax_canary = fig_canary.add_axes([0, 0, 1, 1]) -map_canary = create_map(coords_canary, ax=ax_canary) +fig_canary = create_map(coords_canary, figsize=(8, 3)) plot_cluster_points( longitudes=station_longitudes, latitudes=station_latitudes, clusters=fda_clusters, color_map=fda_color_map, - basemap=map_canary, - ax=ax_canary, + ax=fig_canary.axes[0], ) plt.show() @@ -228,36 +219,30 @@ def plot_cluster_points( # color map to match cluster colors with the previously obtained ones. mv_color_map = { - 0: "red", - 1: "purple", - 2: "yellow", - 3: "green", - 4: "orange", + 0: "yellow", + 1: "orange", + 2: "red", + 3: "purple", + 4: "green", } # Mainland -fig_spain = plt.figure(figsize=(8, 6)) -ax_spain = fig_spain.add_axes([0, 0, 1, 1]) -map_spain = create_map(coords_spain, ax=ax_spain) +fig_spain = create_map(coords_spain, figsize=(8, 6)) plot_cluster_points( longitudes=station_longitudes, latitudes=station_latitudes, clusters=mv_clusters, color_map=mv_color_map, - basemap=map_spain, - ax=ax_spain, + ax=fig_spain.axes[0], ) # Canary Islands -fig_canary = plt.figure(figsize=(8, 3)) -ax_canary = fig_canary.add_axes([0, 0, 1, 1]) -map_canary = create_map(coords_canary, ax=ax_canary) +fig_canary = create_map(coords_canary, figsize=(8, 3)) plot_cluster_points( longitudes=station_longitudes, latitudes=station_latitudes, clusters=mv_clusters, color_map=mv_color_map, - basemap=map_canary, - ax=ax_canary, + ax=fig_canary.axes[0], ) plt.show() diff --git a/pyproject.toml b/pyproject.toml index edf05d3ba..dfd1891d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,9 +46,7 @@ dependencies = [ [project.optional-dependencies] docs = [ - "basemap", - "basemap-data", - "basemap-data-hires", + "cartopy", "ipykernel", "jupyter-sphinx", "myst-parser", From fe6a3696ebca4bae73ded5888b5ae8e126d7bde3 Mon Sep 17 00:00:00 2001 From: VNMabus Date: Tue, 17 Oct 2023 09:24:08 +0200 Subject: [PATCH 192/192] Remove "__init__" from method list in documentation. --- docs/_templates/autosummary/class.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst index fede4ca4e..d1f44b52e 100644 --- a/docs/_templates/autosummary/class.rst +++ b/docs/_templates/autosummary/class.rst @@ -10,12 +10,16 @@ .. autosummary:: {% for item in methods %} + {% if item != "__init__" %} ~{{ name }}.{{ item }} + {% endif %} {%- endfor %} {% endif %} {% for item in methods %} + {% if item != "__init__" %} .. automethod:: {{ item }} + {% endif %} {%- endfor %} {% endblock %}