From 5ed9753bc7d8ce349ac75f9043e61c2f6a20677e Mon Sep 17 00:00:00 2001 From: Sam Levang <39069044+slevang@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:40:27 -0400 Subject: [PATCH 1/7] fix: xarray 2024.09.0 compatility (#227) --- xeofs/base_model.py | 2 +- xeofs/data_container/data_container.py | 2 +- xeofs/preprocessing/preprocessor.py | 1 - xeofs/preprocessing/transformer.py | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/xeofs/base_model.py b/xeofs/base_model.py index 7f13899..16aae46 100644 --- a/xeofs/base_model.py +++ b/xeofs/base_model.py @@ -90,7 +90,7 @@ def serialize(self) -> DataTree: """Serialize a complete model with its preprocessor.""" # Create a root node for this object with its params as attrs ds_root = xr.Dataset(attrs=dict(params=self.get_params())) - dt = DataTree(data=ds_root, name=type(self).__name__) + dt = DataTree(ds_root, name=type(self).__name__) # Retrieve the tree representation of each attached object, or set basic attrs for key, attr in self.get_serialization_attrs().items(): diff --git a/xeofs/data_container/data_container.py b/xeofs/data_container/data_container.py index 9b8f948..2729e02 100644 --- a/xeofs/data_container/data_container.py +++ b/xeofs/data_container/data_container.py @@ -36,7 +36,7 @@ def serialize(self) -> DataTree: for key, data in self.items(): if not data.name: data.name = key - dt[key] = DataTree(data) + dt[key] = DataTree(data.to_dataset()) dt[key].attrs = {key: "_is_node", "allow_compute": self._allow_compute[key]} return dt diff --git a/xeofs/preprocessing/preprocessor.py b/xeofs/preprocessing/preprocessor.py index d258323..9d8a9d5 100644 --- a/xeofs/preprocessing/preprocessor.py +++ b/xeofs/preprocessing/preprocessor.py @@ -391,7 +391,6 @@ def serialize(self) -> DataTree: dt_transformer = transformer_obj.serialize() # Place the serialized transformer in the tree dt[name] = dt_transformer - dt[name].parent = dt return dt diff --git a/xeofs/preprocessing/transformer.py b/xeofs/preprocessing/transformer.py index 6c7935a..6666428 100644 --- a/xeofs/preprocessing/transformer.py +++ b/xeofs/preprocessing/transformer.py @@ -140,7 +140,7 @@ def _serialize(self) -> DataTree: if isinstance(attr, (xr.DataArray, xr.Dataset)): # attach data to data_vars or coords ds = self._serialize_data(key, attr) - dt[key] = DataTree(name=key, data=ds) + dt[key] = DataTree(ds, name=key) dt.attrs[key] = "_is_node" elif isinstance(attr, dict) and any( [isinstance(val, xr.DataArray) for val in attr.values()] @@ -149,7 +149,7 @@ def _serialize(self) -> DataTree: dt_attr = DataTree() for k, v in attr.items(): ds = self._serialize_data(k, v) - dt_attr[k] = DataTree(name=k, data=ds) + dt_attr[k] = DataTree(ds, name=k) dt[key] = dt_attr dt.attrs[key] = "_is_tree" else: From 7cba749418d0313c7404f2a9991675c058f159dc Mon Sep 17 00:00:00 2001 From: Niclas Rieger <45175997+nicrie@users.noreply.github.com> Date: Fri, 13 Sep 2024 01:47:03 +0200 Subject: [PATCH 2/7] fix(cross): correct saving and loading of CPCCA and Rotator models (#225) --- tests/models/cross/test_cpcca.py | 32 ++++++ tests/models/cross/test_hilbert_cpcca.py | 97 +++++++++++++++++++ .../models/cross/test_hilbert_mca_rotator.py | 49 ++++++++++ xeofs/cross/base_model_cross_set.py | 2 + xeofs/cross/cpcca.py | 8 +- xeofs/cross/cpcca_rotator.py | 2 + 6 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 tests/models/cross/test_hilbert_cpcca.py diff --git a/tests/models/cross/test_cpcca.py b/tests/models/cross/test_cpcca.py index 258ff14..fd81282 100644 --- a/tests/models/cross/test_cpcca.py +++ b/tests/models/cross/test_cpcca.py @@ -319,6 +319,38 @@ def test_save_load(tmp_path, engine, alpha): assert np.allclose(XYr_o[1], XYr_l[1]) +@pytest.mark.parametrize("engine", ["netcdf4", "zarr"]) +@pytest.mark.parametrize("alpha", [0.0, 0.5, 1.0]) +def test_save_load_with_data(tmp_path, engine, alpha): + """Test save/load methods in CPCCA class, ensuring that we can + roundtrip the model and get the same results for SCF.""" + X = generate_random_data((200, 10), seed=123) + Y = generate_random_data((200, 20), seed=321) + + original = CPCCA(alpha=alpha) + original.fit(X, Y, "sample") + + # Save the CPCCA model + original.save(tmp_path / "cpcca", engine=engine, save_data=True) + + # Check that the CPCCA model has been saved + assert (tmp_path / "cpcca").exists() + + # Recreate the model from saved file + loaded = CPCCA.load(tmp_path / "cpcca", engine=engine) + + # Check that the params and DataContainer objects match + assert original.get_params() == loaded.get_params() + assert all([key in loaded.data for key in original.data]) + for key in original.data: + assert loaded.data[key].equals(original.data[key]) + + # Test that the recreated model can compute the SCF + assert np.allclose( + original.squared_covariance_fraction(), loaded.squared_covariance_fraction() + ) + + def test_serialize_deserialize_dataarray(mock_data_array): """Test roundtrip serialization when the model is fit on a DataArray.""" model = CPCCA() diff --git a/tests/models/cross/test_hilbert_cpcca.py b/tests/models/cross/test_hilbert_cpcca.py new file mode 100644 index 0000000..02f6455 --- /dev/null +++ b/tests/models/cross/test_hilbert_cpcca.py @@ -0,0 +1,97 @@ +import dask.array as da +import numpy as np +import pytest +import xarray as xr + +from xeofs.cross import HilbertCPCCA + + +def generate_random_data(shape, lazy=False, seed=142): + rng = np.random.default_rng(seed) + if lazy: + return xr.DataArray( + da.random.random(shape, chunks=(5, 5)), + dims=["sample", "feature"], + coords={"sample": np.arange(shape[0]), "feature": np.arange(shape[1])}, + ) + else: + return xr.DataArray( + rng.random(shape), + dims=["sample", "feature"], + coords={"sample": np.arange(shape[0]), "feature": np.arange(shape[1])}, + ) + + +def generate_well_conditioned_data(lazy=False): + rng = np.random.default_rng(142) + t = np.linspace(0, 50, 200) + std = 0.1 + x1 = np.sin(t)[:, None] + rng.normal(0, std, size=(200, 2)) + x2 = np.sin(t)[:, None] + rng.normal(0, std, size=(200, 3)) + x1[:, 1] = x1[:, 1] ** 2 + x2[:, 1] = x2[:, 1] ** 3 + x2[:, 2] = abs(x2[:, 2]) ** (0.5) + coords_time = np.arange(len(t)) + coords_fx = [1, 2] + coords_fy = [1, 2, 3] + X = xr.DataArray( + x1, + dims=["sample", "feature"], + coords={"sample": coords_time, "feature": coords_fx}, + ) + Y = xr.DataArray( + x2, + dims=["sample", "feature"], + coords={"sample": coords_time, "feature": coords_fy}, + ) + if lazy: + X = X.chunk({"sample": 5, "feature": -1}) + Y = Y.chunk({"sample": 5, "feature": -1}) + return X, Y + else: + return X, Y + + +# Currently, netCDF4 does not support complex numbers, so skip this test +@pytest.mark.parametrize("engine", ["zarr"]) +@pytest.mark.parametrize("alpha", [0.0, 0.5, 1.0]) +def test_save_load_with_data(tmp_path, engine, alpha): + """Test save/load methods in CPCCA class, ensuring that we can + roundtrip the model and get the same results.""" + X = generate_random_data((200, 10), seed=123) + Y = generate_random_data((200, 20), seed=321) + + original = HilbertCPCCA(alpha=alpha) + original.fit(X, Y, "sample") + + # Save the CPCCA model + original.save(tmp_path / "cpcca", engine=engine, save_data=True) + + # Check that the CPCCA model has been saved + assert (tmp_path / "cpcca").exists() + + # Recreate the model from saved file + loaded = HilbertCPCCA.load(tmp_path / "cpcca", engine=engine) + + # Check that the params and DataContainer objects match + assert original.get_params() == loaded.get_params() + assert all([key in loaded.data for key in original.data]) + for key in original.data: + assert loaded.data[key].equals(original.data[key]) + + # Test that the recreated model can compute the SCF + assert np.allclose( + original.squared_covariance_fraction(), loaded.squared_covariance_fraction() + ) + + # Test that the recreated model can compute the components amplitude + A1_original, A2_original = original.components_amplitude() + A1_loaded, A2_loaded = loaded.components_amplitude() + assert np.allclose(A1_original, A1_loaded) + assert np.allclose(A2_original, A2_loaded) + + # Test that the recreated model can compute the components phase + P1_original, P2_original = original.components_phase() + P1_loaded, P2_loaded = loaded.components_phase() + assert np.allclose(P1_original, P1_loaded) + assert np.allclose(P2_original, P2_loaded) diff --git a/tests/models/cross/test_hilbert_mca_rotator.py b/tests/models/cross/test_hilbert_mca_rotator.py index 7ef22bf..2172028 100644 --- a/tests/models/cross/test_hilbert_mca_rotator.py +++ b/tests/models/cross/test_hilbert_mca_rotator.py @@ -231,3 +231,52 @@ def test_scores_phase(mca_model, mock_data_array, dim): mca_rotator = HilbertMCARotator(n_modes=2) mca_rotator.fit(mca_model) amps1, amps2 = mca_rotator.scores_phase() + + +@pytest.mark.parametrize( + "dim", + [ + (("time",)), + (("lat", "lon")), + (("lon", "lat")), + ], +) +# Currently, netCDF4 does not support complex numbers, so skip this test +@pytest.mark.parametrize("engine", ["zarr"]) +def test_save_load_with_data(tmp_path, engine, mca_model): + """Test save/load methods in HilbertMCARotator class, ensuring that we can + roundtrip the model and get the same results.""" + original = HilbertMCARotator(n_modes=2) + original.fit(mca_model) + + # Save the HilbertMCARotator model + original.save(tmp_path / "mca", engine=engine, save_data=True) + + # Check that the HilbertMCARotator model has been saved + assert (tmp_path / "mca").exists() + + # Recreate the model from saved file + loaded = HilbertMCARotator.load(tmp_path / "mca", engine=engine) + + # Check that the params and DataContainer objects match + assert original.get_params() == loaded.get_params() + assert all([key in loaded.data for key in original.data]) + for key in original.data: + assert loaded.data[key].equals(original.data[key]) + + # Test that the recreated model can compute the SCF + assert np.allclose( + original.squared_covariance_fraction(), loaded.squared_covariance_fraction() + ) + + # Test that the recreated model can compute the components amplitude + A1_original, A2_original = original.components_amplitude() + A1_loaded, A2_loaded = loaded.components_amplitude() + assert np.allclose(A1_original, A1_loaded) + assert np.allclose(A2_original, A2_loaded) + + # Test that the recreated model can compute the components phase + P1_original, P2_original = original.components_phase() + P1_loaded, P2_loaded = loaded.components_phase() + assert np.allclose(P1_original, P1_loaded) + assert np.allclose(P2_original, P2_loaded) diff --git a/xeofs/cross/base_model_cross_set.py b/xeofs/cross/base_model_cross_set.py index a7d8bdb..352106e 100644 --- a/xeofs/cross/base_model_cross_set.py +++ b/xeofs/cross/base_model_cross_set.py @@ -511,6 +511,8 @@ def get_serialization_attrs(self) -> dict: preprocessor2=self.preprocessor2, whitener1=self.whitener1, whitener2=self.whitener2, + sample_name=self.sample_name, + feature_name=self.feature_name, ) def _augment_data(self, X: DataArray, Y: DataArray) -> tuple[DataArray, DataArray]: diff --git a/xeofs/cross/cpcca.py b/xeofs/cross/cpcca.py index 11cc914..ad88808 100644 --- a/xeofs/cross/cpcca.py +++ b/xeofs/cross/cpcca.py @@ -1218,8 +1218,8 @@ def components_phase(self, normalized=True) -> tuple[DataObject, DataObject]: Px = self.whitener1.inverse_transform_components(Px) Py = self.whitener2.inverse_transform_components(Py) - Px = xr.apply_ufunc(np.angle, Px, keep_attrs=True) - Py = xr.apply_ufunc(np.angle, Py, keep_attrs=True) + Px = xr.apply_ufunc(np.angle, Px, keep_attrs=True, dask="allowed") + Py = xr.apply_ufunc(np.angle, Py, keep_attrs=True, dask="allowed") Px.name = "components_phase_X" Py.name = "components_phase_Y" @@ -1288,8 +1288,8 @@ def scores_phase(self, normalized=False) -> tuple[DataArray, DataArray]: Rx = self.whitener1.inverse_transform_scores(Rx) Ry = self.whitener2.inverse_transform_scores(Ry) - Rx = xr.apply_ufunc(np.angle, Rx, keep_attrs=True) - Ry = xr.apply_ufunc(np.angle, Ry, keep_attrs=True) + Rx = xr.apply_ufunc(np.angle, Rx, keep_attrs=True, dask="allowed") + Ry = xr.apply_ufunc(np.angle, Ry, keep_attrs=True, dask="allowed") Rx.name = "scores_phase_X" Ry.name = "scores_phase_Y" diff --git a/xeofs/cross/cpcca_rotator.py b/xeofs/cross/cpcca_rotator.py index 162157c..1e2fd0d 100644 --- a/xeofs/cross/cpcca_rotator.py +++ b/xeofs/cross/cpcca_rotator.py @@ -111,6 +111,8 @@ def get_serialization_attrs(self) -> dict: whitener2=self.whitener2, model=self.model, sorted=self.sorted, + sample_name=self.sample_name, + feature_name=self.feature_name, ) def _fit_algorithm(self, model) -> Self: From e2525bb0f602337589d357860e0f8aeb01a8f58b Mon Sep 17 00:00:00 2001 From: Niclas Rieger <45175997+nicrie@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:07:38 +0200 Subject: [PATCH 3/7] perf: avoid redundant data when saving Rotator models (#229) --- .../models/cross/test_hilbert_mca_rotator.py | 2 +- tests/models/cross/test_mca_rotator.py | 2 +- tests/models/single/test_eof_rotator.py | 6 +-- .../models/single/test_hilbert_eof_rotator.py | 6 +-- xeofs/cross/cpcca_rotator.py | 46 +++++++++---------- xeofs/cross/mca_rotator.py | 3 -- xeofs/single/eof_rotator.py | 17 ++++--- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/tests/models/cross/test_hilbert_mca_rotator.py b/tests/models/cross/test_hilbert_mca_rotator.py index 2172028..c1e4752 100644 --- a/tests/models/cross/test_hilbert_mca_rotator.py +++ b/tests/models/cross/test_hilbert_mca_rotator.py @@ -40,7 +40,7 @@ def test_fit(mca_model): mca_rotator = HilbertMCARotator(n_modes=2) mca_rotator.fit(mca_model) - assert hasattr(mca_rotator, "model") + assert hasattr(mca_rotator, "model_data") assert hasattr(mca_rotator, "data") diff --git a/tests/models/cross/test_mca_rotator.py b/tests/models/cross/test_mca_rotator.py index f467d33..9c86608 100644 --- a/tests/models/cross/test_mca_rotator.py +++ b/tests/models/cross/test_mca_rotator.py @@ -43,7 +43,7 @@ def test_fit(mca_model): mca_rotator = MCARotator(n_modes=4) mca_rotator.fit(mca_model) - assert hasattr(mca_rotator, "model") + assert hasattr(mca_rotator, "model_data") assert hasattr(mca_rotator, "data") diff --git a/tests/models/single/test_eof_rotator.py b/tests/models/single/test_eof_rotator.py index b8e8424..22c426e 100644 --- a/tests/models/single/test_eof_rotator.py +++ b/tests/models/single/test_eof_rotator.py @@ -45,12 +45,12 @@ def test_fit(eof_model): eof_rotator.fit(eof_model) assert hasattr( - eof_rotator, "model" - ), 'The attribute "model" should be populated after fitting.' + eof_rotator, "model_data" + ), 'The attribute "model_data" should be populated after fitting.' assert hasattr( eof_rotator, "data" ), 'The attribute "data" should be populated after fitting.' - assert isinstance(eof_rotator.model, EOF) + assert isinstance(eof_rotator.model_data, DataContainer) assert isinstance(eof_rotator.data, DataContainer) diff --git a/tests/models/single/test_hilbert_eof_rotator.py b/tests/models/single/test_hilbert_eof_rotator.py index 9fc3bd3..b90960c 100644 --- a/tests/models/single/test_hilbert_eof_rotator.py +++ b/tests/models/single/test_hilbert_eof_rotator.py @@ -42,12 +42,12 @@ def test_fit(ceof_model): ceof_rotator.fit(ceof_model) assert hasattr( - ceof_rotator, "model" - ), 'The attribute "model" should be populated after fitting.' + ceof_rotator, "model_data" + ), 'The attribute "model_data" should be populated after fitting.' assert hasattr( ceof_rotator, "data" ), 'The attribute "data" should be populated after fitting.' - assert isinstance(ceof_rotator.model, HilbertEOF) + assert isinstance(ceof_rotator.model_data, DataContainer) assert isinstance(ceof_rotator.data, DataContainer) diff --git a/xeofs/cross/cpcca_rotator.py b/xeofs/cross/cpcca_rotator.py index 1e2fd0d..218759c 100644 --- a/xeofs/cross/cpcca_rotator.py +++ b/xeofs/cross/cpcca_rotator.py @@ -98,35 +98,34 @@ def __init__( self.whitener1 = Whitener() self.whitener2 = Whitener() self.data = DataContainer() - self.model = CPCCA() + self.model_data = DataContainer() self.sorted = False def get_serialization_attrs(self) -> dict: return dict( data=self.data, + model_data=self.model_data, preprocessor1=self.preprocessor1, preprocessor2=self.preprocessor2, whitener1=self.whitener1, whitener2=self.whitener2, - model=self.model, sorted=self.sorted, sample_name=self.sample_name, feature_name=self.feature_name, ) def _fit_algorithm(self, model) -> Self: - self.model = model self.preprocessor1 = model.preprocessor1 self.preprocessor2 = model.preprocessor2 self.whitener1 = model.whitener1 self.whitener2 = model.whitener2 - self.sample_name = self.model.sample_name - self.feature_name = self.model.feature_name + self.sample_name = model.sample_name + self.feature_name = model.feature_name self.sorted = False common_feature_dim = "common_feature_dim" - feature_name = self._get_feature_name() + feature_name = model.feature_name n_modes = self._params["n_modes"] power = self._params["power"] @@ -145,12 +144,12 @@ def _fit_algorithm(self, model) -> Self: # fraction" which is conserved under rotation, but does not have a clear # interpretation as the term covariance fraction is only correct when # both data sets X and Y are equal and MCA reduces to PCA. - svalues = self.model.data["singular_values"].sel(mode=slice(1, n_modes)) + svalues = model.data["singular_values"].sel(mode=slice(1, n_modes)) scaling = np.sqrt(svalues) # Get unrotated singular vectors - Qx = self.model.data["components1"].sel(mode=slice(1, n_modes)) - Qy = self.model.data["components2"].sel(mode=slice(1, n_modes)) + Qx = model.data["components1"].sel(mode=slice(1, n_modes)) + Qy = model.data["components2"].sel(mode=slice(1, n_modes)) # Unwhiten and back-transform into physical space Qx = self.whitener1.inverse_transform_components(Qx) @@ -233,8 +232,8 @@ def _fit_algorithm(self, model) -> Self: idx_modes_sorted.coords.update(squared_covariance.coords) # Rotate scores using rotation matrix - scores1 = self.model.data["scores1"].sel(mode=slice(1, n_modes)) - scores2 = self.model.data["scores2"].sel(mode=slice(1, n_modes)) + scores1 = model.data["scores1"].sel(mode=slice(1, n_modes)) + scores2 = model.data["scores2"].sel(mode=slice(1, n_modes)) scores1 = self.whitener1.inverse_transform_scores(scores1) scores2 = self.whitener2.inverse_transform_scores(scores2) @@ -260,12 +259,18 @@ def _fit_algorithm(self, model) -> Self: scores1_rot = scores1_rot * modes_sign scores2_rot = scores2_rot * modes_sign - # Create data container + # Create data container for Rotator and original model data + self.model_data.add(name="singular_values", data=model.data["singular_values"]) + self.model_data.add(name="components1", data=model.data["components1"]) + self.model_data.add(name="components2", data=model.data["components2"]) + + # Assigning input data to the Rotator object allows us to inherit some functionalities from the original model + # like squared_covariance_fraction(), homogeneous_patterns() etc. self.data.add( - name="input_data1", data=self.model.data["input_data1"], allow_compute=False + name="input_data1", data=model.data["input_data1"], allow_compute=False ) self.data.add( - name="input_data2", data=self.model.data["input_data2"], allow_compute=False + name="input_data2", data=model.data["input_data2"], allow_compute=False ) self.data.add(name="components1", data=Qx_rot) self.data.add(name="components2", data=Qy_rot) @@ -274,7 +279,7 @@ def _fit_algorithm(self, model) -> Self: self.data.add(name="squared_covariance", data=squared_covariance) self.data.add( name="total_squared_covariance", - data=self.model.data["total_squared_covariance"], + data=model.data["total_squared_covariance"], ) self.data.add(name="idx_modes_sorted", data=idx_modes_sorted) @@ -337,14 +342,14 @@ def transform( ) RinvT = RinvT.rename({"mode_n": "mode"}) - scaling = self.model.data["singular_values"].sel(mode=slice(1, n_modes)) + scaling = self.model_data["singular_values"].sel(mode=slice(1, n_modes)) scaling = np.sqrt(scaling) results = [] if X is not None: # Select the (non-rotated) singular vectors of the first dataset - comps1 = self.model.data["components1"].sel(mode=slice(1, n_modes)) + comps1 = self.model_data["components1"].sel(mode=slice(1, n_modes)) # Preprocess the data comps1 = self.whitener1.inverse_transform_components(comps1) @@ -374,7 +379,7 @@ def transform( if Y is not None: # Select the (non-rotated) singular vectors of the second dataset - comps2 = self.model.data["components2"].sel(mode=slice(1, n_modes)) + comps2 = self.model_data["components2"].sel(mode=slice(1, n_modes)) # Preprocess the data comps2 = self.whitener2.inverse_transform_components(comps2) @@ -451,9 +456,6 @@ def _compute_rot_mat_inv_trans(self, rotation_matrix, input_dims) -> xr.DataArra rotation_matrix = rotation_matrix.conj().transpose(*input_dims) return rotation_matrix - def _get_feature_name(self): - return self.model.feature_name - class ComplexCPCCARotator(CPCCARotator, ComplexCPCCA): """Rotate a solution obtained from ``xe.cross.ComplexCPCCA``. @@ -517,7 +519,6 @@ class ComplexCPCCARotator(CPCCARotator, ComplexCPCCA): def __init__(self, **kwargs): CPCCARotator.__init__(self, **kwargs) self.attrs.update({"model": "Rotated Complex CPCCA"}) - self.model = ComplexCPCCA() class HilbertCPCCARotator(ComplexCPCCARotator, HilbertCPCCA): @@ -582,7 +583,6 @@ class HilbertCPCCARotator(ComplexCPCCARotator, HilbertCPCCA): def __init__(self, **kwargs): ComplexCPCCARotator.__init__(self, **kwargs) self.attrs.update({"model": "Rotated Hilbert CPCCA"}) - self.model = HilbertCPCCA() def transform( self, X: DataObject | None = None, Y: DataObject | None = None, normalized=False diff --git a/xeofs/cross/mca_rotator.py b/xeofs/cross/mca_rotator.py index a47146c..be2127a 100644 --- a/xeofs/cross/mca_rotator.py +++ b/xeofs/cross/mca_rotator.py @@ -72,7 +72,6 @@ def __init__( # Define analysis-relevant meta data self.attrs.update({"model": "Rotated MCA"}) - self.model = MCA() class ComplexMCARotator(ComplexCPCCARotator, ComplexMCA): @@ -149,7 +148,6 @@ def __init__( compute=compute, ) self.attrs.update({"model": "Rotated Complex MCA"}) - self.model = ComplexMCA() class HilbertMCARotator(HilbertCPCCARotator, HilbertMCA): @@ -226,4 +224,3 @@ def __init__( compute=compute, ) self.attrs.update({"model": "Rotated Hilbert MCA"}) - self.model = HilbertMCA() diff --git a/xeofs/single/eof_rotator.py b/xeofs/single/eof_rotator.py index b321189..4cc6f38 100644 --- a/xeofs/single/eof_rotator.py +++ b/xeofs/single/eof_rotator.py @@ -88,7 +88,7 @@ def __init__( # Attach empty objects self.preprocessor = Preprocessor() self.data = DataContainer() - self.model = EOF() + self.model_data = DataContainer() self.sorted = False @@ -96,7 +96,7 @@ def get_serialization_attrs(self) -> dict: return dict( data=self.data, preprocessor=self.preprocessor, - model=self.model, + model_data=self.model_data, sorted=self.sorted, ) @@ -117,7 +117,6 @@ def fit(self, model) -> Self: return self def _fit_algorithm(self, model) -> Self: - self.model = model self.preprocessor = model.preprocessor self.sample_name = model.sample_name self.feature_name = model.feature_name @@ -189,6 +188,10 @@ def _fit_algorithm(self, model) -> Self: scores = scores * modes_sign # Store the results + self.model_data.add(model.data["norms"], "singular_values") + self.model_data.add(model.data["components"], "components") + + # Assigning input data to the Rotator object allows us to inherit some functionalities from the original model self.data.add(model.data["input_data"], "input_data", allow_compute=False) self.data.add(rot_components, "components") self.data.add(scores, "scores") @@ -224,10 +227,12 @@ def _sort_by_variance(self): def _transform_algorithm(self, X: DataArray) -> DataArray: n_modes = self._params["n_modes"] - svals = self.model.singular_values().sel(mode=slice(1, self._params["n_modes"])) + svals = self.model_data["singular_values"].sel( + mode=slice(1, self._params["n_modes"]) + ) pseudo_norms = self.data["norms"] # Select the (non-rotated) singular vectors of the first dataset - components = self.model.data["components"].sel(mode=slice(1, n_modes)) + components = self.model_data["components"].sel(mode=slice(1, n_modes)) # Compute non-rotated scores by projecting the data onto non-rotated components projections = xr.dot(X, components) / svals @@ -329,7 +334,6 @@ def __init__( n_modes=n_modes, power=power, max_iter=max_iter, rtol=rtol, compute=compute ) self.attrs.update({"model": "Rotated Complex EOF analysis"}) - self.model = ComplexEOF() class HilbertEOFRotator(EOFRotator, HilbertEOF): @@ -385,7 +389,6 @@ def __init__( n_modes=n_modes, power=power, max_iter=max_iter, rtol=rtol, compute=compute ) self.attrs.update({"model": "Rotated Hilbert EOF analysis"}) - self.model = HilbertEOF() def _transform_algorithm(self, data: DataArray) -> DataArray: # Here we leverage the Method Resolution Order (MRO) to invoke the From bcbaa9effd0515f8d8ee1367e0f94a8cd5c771f4 Mon Sep 17 00:00:00 2001 From: Niclas Rieger <45175997+nicrie@users.noreply.github.com> Date: Sat, 14 Sep 2024 00:01:10 +0200 Subject: [PATCH 4/7] perf: speed up rotation by about 20% (#228) --- xeofs/linalg/_numpy/_rotation.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/xeofs/linalg/_numpy/_rotation.py b/xeofs/linalg/_numpy/_rotation.py index 7f7f6f4..068735a 100644 --- a/xeofs/linalg/_numpy/_rotation.py +++ b/xeofs/linalg/_numpy/_rotation.py @@ -161,16 +161,15 @@ def _varimax( # Seek for rotation matrix based on varimax criteria delta = 0.0 + XH = X.conj().T + alpha = gamma / n_samples for i in range(max_iter): delta_old = delta basis = X @ R basis2 = basis * basis.conj() - basis3 = basis2 * basis - W = np.diag(np.sum(basis2, axis=0)) - alpha = gamma / n_samples - - transformed = X.conj().T @ (basis3 - (alpha * basis @ W)) + W = np.sum(basis2, axis=0) + transformed = XH @ (basis * (basis2 - (alpha * W))) U, svals, VT = svd_func(transformed, *svd_args) R = U @ VT delta = np.sum(svals) From 3ba531c62a753feecec15d24ceabba317c7256e2 Mon Sep 17 00:00:00 2001 From: Niclas Rieger <45175997+nicrie@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:12:30 +0200 Subject: [PATCH 5/7] fix: correct whitening in HilbertCPCCA models (#230) --- tests/models/cross/test_hilbert_cpcca.py | 12 ++ tests/models/single/test_pop.py | 2 +- tests/preprocessing/test_pca.py | 146 ++++++++++++++++++ tests/preprocessing/test_whitener.py | 182 +++++------------------ xeofs/cross/base_model_cross_set.py | 48 ++++-- xeofs/cross/cpcca.py | 46 ++++-- xeofs/cross/cpcca_rotator.py | 14 +- xeofs/preprocessing/__init__.py | 2 + xeofs/preprocessing/pca.py | 181 ++++++++++++++++++++++ xeofs/preprocessing/whitener.py | 127 +++------------- xeofs/single/pop.py | 23 ++- 11 files changed, 499 insertions(+), 284 deletions(-) create mode 100644 tests/preprocessing/test_pca.py create mode 100644 xeofs/preprocessing/pca.py diff --git a/tests/models/cross/test_hilbert_cpcca.py b/tests/models/cross/test_hilbert_cpcca.py index 02f6455..d30b449 100644 --- a/tests/models/cross/test_hilbert_cpcca.py +++ b/tests/models/cross/test_hilbert_cpcca.py @@ -52,6 +52,18 @@ def generate_well_conditioned_data(lazy=False): return X, Y +@pytest.mark.parametrize("use_pca", [True, False]) +def test_singular_values(use_pca): + """Test that the singular values of the Hilbert CCA are less than 1.""" + X, Y = generate_well_conditioned_data() + cpcca = HilbertCPCCA(n_modes=2, alpha=0.0, use_pca=use_pca, n_pca_modes=2) + cpcca.fit(X, Y, "sample") + s_values = cpcca.data["singular_values"] + + # Singular values are the canonical correlations, so they should be less than 1 + assert np.all(s_values <= 1) + + # Currently, netCDF4 does not support complex numbers, so skip this test @pytest.mark.parametrize("engine", ["zarr"]) @pytest.mark.parametrize("alpha", [0.0, 0.5, 1.0]) diff --git a/tests/models/single/test_pop.py b/tests/models/single/test_pop.py index b42e974..fe9f9b4 100644 --- a/tests/models/single/test_pop.py +++ b/tests/models/single/test_pop.py @@ -12,7 +12,7 @@ def test_init(): # Assert preprocessor has been initialized assert hasattr(pop, "_params") assert hasattr(pop, "preprocessor") - assert hasattr(pop, "whitener") + assert hasattr(pop, "pca") def test_fit(mock_data_array): diff --git a/tests/preprocessing/test_pca.py b/tests/preprocessing/test_pca.py new file mode 100644 index 0000000..dec39f5 --- /dev/null +++ b/tests/preprocessing/test_pca.py @@ -0,0 +1,146 @@ +import numpy as np +import pytest +import xarray as xr + +from xeofs.preprocessing import PCA + +from ..utilities import ( + assert_expected_coords, + assert_expected_dims, + data_is_dask, +) + +# ============================================================================= +# GENERALLY VALID TEST CASES +# ============================================================================= +N_SAMPLE_DIMS = [1] +N_FEATURE_DIMS = [1] +INDEX_POLICY = ["index"] +NAN_POLICY = ["no_nan"] +DASK_POLICY = ["no_dask", "dask"] +SEED = [0] + +VALID_TEST_DATA = [ + (ns, nf, index, nan, dask) + for ns in N_SAMPLE_DIMS + for nf in N_FEATURE_DIMS + for index in INDEX_POLICY + for nan in NAN_POLICY + for dask in DASK_POLICY +] + + +def generate_well_conditioned_data(lazy=False): + t = np.linspace(0, 50, 200) + std = 0.1 + X = np.sin(t)[:, None] + np.random.normal(0, std, size=(200, 3)) + X[:, 1] = X[:, 1] ** 3 + X[:, 2] = abs(X[:, 2]) ** (0.5) + X = xr.DataArray( + X, + dims=["sample", "feature"], + coords={"sample": np.arange(200), "feature": np.arange(3)}, + name="X", + ) + X = X - X.mean("sample") + if lazy: + X = X.chunk({"sample": 5, "feature": -1}) + return X + + +# TESTS +# ============================================================================= +@pytest.mark.parametrize("lazy", [False, True]) +def test_fit(lazy): + data = generate_well_conditioned_data(lazy) + + pca = PCA(n_modes=2) + pca.fit(data) + + +@pytest.mark.parametrize("lazy", [False, True]) +@pytest.mark.parametrize("use_pca", [True, False]) +def test_transform(lazy, use_pca): + data = generate_well_conditioned_data(lazy) + + pca = PCA(n_modes=2, use_pca=use_pca) + pca.fit(data) + + # Transform data + transformed_data = pca.transform(data) + transformed_data2 = pca.transform(data) + assert transformed_data.identical(transformed_data2) + + assert isinstance(transformed_data, xr.DataArray) + assert transformed_data.ndim == 2 + assert transformed_data.dims == ("sample", "feature") + + # Consistent dask behaviour + is_dask_before = data_is_dask(data) + is_dask_after = data_is_dask(transformed_data) + assert is_dask_before == is_dask_after + + +@pytest.mark.parametrize("lazy", [False, True]) +@pytest.mark.parametrize("use_pca", [True, False]) +def test_fit_transform(lazy, use_pca): + data = generate_well_conditioned_data(lazy) + + pca = PCA(n_modes=2, use_pca=use_pca) + + # Transform data + transformed_data = pca.fit_transform(data) + transformed_data2 = pca.transform(data) + assert transformed_data.identical(transformed_data2) + + assert isinstance(transformed_data, xr.DataArray) + assert transformed_data.ndim == 2 + assert transformed_data.dims == ("sample", "feature") + + # Consistent dask behaviour + is_dask_before = data_is_dask(data) + is_dask_after = data_is_dask(transformed_data) + assert is_dask_before == is_dask_after + + +@pytest.mark.parametrize("lazy", [False, True]) +@pytest.mark.parametrize("use_pca", [True, False]) +def test_invserse_transform_data(lazy, use_pca): + data = generate_well_conditioned_data(lazy) + + pca = PCA(n_modes=2, use_pca=use_pca) + pca.fit(data) + + transformed = pca.transform(data) + untransformed = pca.inverse_transform_data(transformed) + + is_dask_before = data_is_dask(data) + is_dask_after = data_is_dask(untransformed) + + # Unstacked data has dimensions of original data + assert_expected_dims(data, untransformed, policy="all") + # Unstacked data has coordinates of original data + assert_expected_coords(data, untransformed, policy="all") + # inverse transform should not change dask-ness + assert is_dask_before == is_dask_after + + +@pytest.mark.parametrize("n_modes", [1, 2, 3]) +def test_transform_pca_n_modes(n_modes): + data = generate_well_conditioned_data() + + pca = PCA(use_pca=True, n_modes=n_modes) + transformed = pca.fit_transform(data) + + # PCA reduces dimensionality + assert transformed.shape[1] == n_modes + + +@pytest.mark.parametrize("use_pca", [True, False]) +def test_transform_keep_coordinates(use_pca): + X = generate_well_conditioned_data() + + pca = PCA(use_pca=use_pca, n_modes="all") + transformed = pca.fit_transform(X) + + assert len(transformed.coords) == len(X.coords) diff --git a/tests/preprocessing/test_whitener.py b/tests/preprocessing/test_whitener.py index a1511b9..54b82ea 100644 --- a/tests/preprocessing/test_whitener.py +++ b/tests/preprocessing/test_whitener.py @@ -60,43 +60,51 @@ def generate_well_conditioned_data(lazy=False): def test_fit(lazy): data = generate_well_conditioned_data(lazy) - whitener = Whitener(n_modes=2) + whitener = Whitener() whitener.fit(data) -@pytest.mark.parametrize( - "lazy", - [False, True], -) -def test_transform(lazy): +@pytest.mark.parametrize("lazy", [False, True]) +@pytest.mark.parametrize("alpha", [0.0, 0.5, 1.0, 1.5]) +def test_transform(lazy, alpha): data = generate_well_conditioned_data(lazy) - whitener = Whitener(n_modes=2) + whitener = Whitener() whitener.fit(data) # Transform data transformed_data = whitener.transform(data) transformed_data2 = whitener.transform(data) - assert transformed_data.identical(transformed_data2) assert isinstance(transformed_data, xr.DataArray) assert transformed_data.ndim == 2 assert transformed_data.dims == ("sample", "feature") + # Transformed data is identical + assert transformed_data.identical(transformed_data2) + + # Check that for full whitening, transformed data is uncorrelated + # and has variance one + if math.isclose(alpha, 0.0, abs_tol=1e-6): + # Transformed data has variance = 1 + assert np.allclose(transformed_data.var("sample").values, 1.0) + + # Transformed data is uncorrelated + C = np.corrcoef(transformed_data.values, rowvar=False) + target = np.identity(data.shape[1]) + np.testing.assert_allclose(C, target, atol=1e-6) + # Consistent dask behaviour is_dask_before = data_is_dask(data) is_dask_after = data_is_dask(transformed_data) assert is_dask_before == is_dask_after -@pytest.mark.parametrize( - "lazy", - [False, True], -) +@pytest.mark.parametrize("lazy", [False, True]) def test_fit_transform(lazy): data = generate_well_conditioned_data(lazy) - whitener = Whitener(n_modes=2) + whitener = Whitener() # Transform data transformed_data = whitener.fit_transform(data) @@ -113,14 +121,12 @@ def test_fit_transform(lazy): assert is_dask_before == is_dask_after -@pytest.mark.parametrize( - "lazy", - [False, True], -) -def test_invserse_transform_data(lazy): +@pytest.mark.parametrize("lazy", [False, True]) +@pytest.mark.parametrize("alpha", [0.0, 0.5, 1.0, 1.5]) +def test_inverse_transform_data(lazy, alpha): data = generate_well_conditioned_data(lazy) - whitener = Whitener(n_modes=2) + whitener = Whitener(alpha) whitener.fit(data) whitened_data = whitener.transform(data) @@ -136,55 +142,8 @@ def test_invserse_transform_data(lazy): # inverse transform should not change dask-ness assert is_dask_before == is_dask_after - -@pytest.mark.parametrize( - "alpha,use_pca", - [ - (0.0, False), - (0.5, False), - (1.0, False), - (1.5, False), - (0.0, True), - (0.5, True), - (1.0, True), - (1.5, True), - ], -) -def test_transform_alpha(alpha, use_pca): - data = data = generate_well_conditioned_data() - - whitener = Whitener(alpha=alpha, use_pca=use_pca) - data_whitened = whitener.fit_transform(data) - - nc = data.shape[0] - 1 - norm = (data_whitened**2).sum("sample") / nc - ones = norm / norm - # Check that for alpha=0 full whitening is performed - if math.isclose(alpha, 0.0, abs_tol=1e-6): - xr.testing.assert_allclose(norm, ones, atol=1e-6) - - -@pytest.mark.parametrize( - "alpha,use_pca", - [ - (0.0, False), - (0.5, False), - (1.0, False), - (1.5, False), - (0.0, True), - (0.5, True), - (1.0, True), - (1.5, True), - ], -) -def test_inverse_transform_alpha(alpha, use_pca): - # Use data with more samples than features and high signal-to-noise ratio - data = generate_well_conditioned_data() - whitener = Whitener(alpha=alpha, use_pca=use_pca) - data_whitened = whitener.fit_transform(data) - data_unwhitened = whitener.inverse_transform_data(data_whitened) - - xr.testing.assert_allclose(data, data_unwhitened, atol=1e-6) + # Unwhitened data is identical to original data + xr.testing.assert_allclose(data, unwhitened_data, atol=1e-6) def test_invalid_alpha(): @@ -193,7 +152,7 @@ def test_invalid_alpha(): err_msg = "`alpha` must be greater than or equal to 0" with pytest.raises(ValueError, match=err_msg): - Whitener(n_modes=2, alpha=-1.0) + Whitener(alpha=-1.0) def test_raise_warning_ill_conditioned(): @@ -207,24 +166,10 @@ def test_raise_warning_ill_conditioned(): _ = whitener.fit_transform(data) -@pytest.mark.parametrize( - "n_modes", - [1, 2, 3], -) -def test_transform_pca_n_modes(n_modes): - data = generate_well_conditioned_data() - - whitener = Whitener(use_pca=True, n_modes=n_modes) - transformed = whitener.fit_transform(data) - - # PCA-Whitener reduces dimensionality - assert transformed.shape[1] == n_modes - - def test_whitener_identity_transformation(): data = generate_well_conditioned_data() - whitener = Whitener(alpha=1.0, use_pca=False) + whitener = Whitener(alpha=1.0) transformed = whitener.fit_transform(data) reconstructed = whitener.inverse_transform_data(transformed) @@ -233,20 +178,8 @@ def test_whitener_identity_transformation(): xr.testing.assert_identical(data, reconstructed) -@pytest.mark.parametrize( - "alpha,use_pca", - [ - (0.0, True), - (0.5, True), - (1.0, True), - (1.5, True), - (0.0, False), - (0.5, False), - (1.0, False), - (1.5, False), - ], -) -def test_unwhiten_cross_covariance_matrix(alpha, use_pca): +@pytest.mark.parametrize("alpha", [0.0, 0.5, 1.0, 1.5]) +def test_unwhiten_cross_covariance_matrix(alpha): def unwhiten_cov_mat(Cw, S1_inv, S2_inv): n_dims_s1 = len(S1_inv.shape) n_dims_s2 = len(S2_inv.shape) @@ -254,8 +187,6 @@ def unwhiten_cov_mat(Cw, S1_inv, S2_inv): match n_dims_s1: case 0: C_unwhitened = Cw - case 1: - C_unwhitened = S1_inv[:, None] * Cw case 2: C_unwhitened = S1_inv @ Cw case _: @@ -264,8 +195,6 @@ def unwhiten_cov_mat(Cw, S1_inv, S2_inv): match n_dims_s2: case 0: pass - case 1: - C_unwhitened = C_unwhitened * S2_inv[None, :] case 2: C_unwhitened = C_unwhitened @ S2_inv case _: @@ -273,18 +202,18 @@ def unwhiten_cov_mat(Cw, S1_inv, S2_inv): return C_unwhitened - """Test that we can uncover the original total amount of covariance between two datasets after whitening.""" + """Test that we can uncover the original total amount of squared covariance between two datasets after whitening.""" data1 = generate_well_conditioned_data() data2 = generate_well_conditioned_data() ** 2 - whitener1 = Whitener(alpha=0.5, use_pca=True, n_modes="all") - whitener2 = Whitener(alpha=alpha, use_pca=use_pca, n_modes="all") + whitener1 = Whitener(alpha=0.5) + whitener2 = Whitener(alpha=alpha) transformed1 = whitener1.fit_transform(data1) transformed2 = whitener2.fit_transform(data2) - S1_inv = whitener1.get_Tinv(unwhiten_only=True).values - S2_inv = whitener2.get_Tinv(unwhiten_only=True).values + S1_inv = whitener1.Tinv.values + S2_inv = whitener2.Tinv.values C = data1.values.T @ data2.values Cw = transformed1.values.T @ transformed2.values @@ -296,44 +225,11 @@ def unwhiten_cov_mat(Cw, S1_inv, S2_inv): np.testing.assert_almost_equal(total_covariance, total_covariance_rec) -@pytest.mark.parametrize( - "use_pca", - [True, False], -) -def test_standardize_and_decorrelate(use_pca): - X = generate_well_conditioned_data() - n_features = X.shape[1] - - whitener = Whitener(alpha=0.0, use_pca=use_pca, n_modes="all") - transformed = whitener.fit_transform(X) - - # Check that transformed data is standardized - assert np.allclose(transformed.mean("sample").values, np.zeros(n_features)) - assert np.allclose(transformed.std("sample").values, np.ones(n_features), rtol=1e-2) - - # Check that transformed data is decorrelated - C = np.corrcoef(transformed.values, rowvar=False) - target = np.identity(n_features) - np.testing.assert_allclose(C, target, atol=1e-6) - - -@pytest.mark.parametrize( - "alpha,use_pca", - [ - (0.0, True), - (0.5, True), - (1.0, True), - (1.5, True), - (0.0, False), - (0.5, False), - (1.0, False), - (1.5, False), - ], -) -def test_transform_keep_coordinates(alpha, use_pca): +@pytest.mark.parametrize("alpha", [0.0, 0.5, 1.0, 1.5]) +def test_transform_keep_coordinates(alpha): X = generate_well_conditioned_data() - whitener = Whitener(alpha=alpha, use_pca=use_pca, n_modes="all") + whitener = Whitener(alpha=alpha) transformed = whitener.fit_transform(X) assert len(transformed.coords) == len(X.coords) diff --git a/xeofs/cross/base_model_cross_set.py b/xeofs/cross/base_model_cross_set.py index 352106e..f86b63c 100644 --- a/xeofs/cross/base_model_cross_set.py +++ b/xeofs/cross/base_model_cross_set.py @@ -6,8 +6,7 @@ from ..base_model import BaseModel from ..data_container import DataContainer -from ..preprocessing.preprocessor import Preprocessor -from ..preprocessing.whitener import Whitener +from ..preprocessing import PCA, Preprocessor, Whitener from ..utils.data_types import DataArray, DataObject, GenericType from ..utils.sanity_checks import validate_input_type from ..utils.xarray_utils import convert_to_dim_type @@ -163,19 +162,29 @@ def __init__( compute=compute, ) - self.whitener1 = Whitener( - alpha=alpha[0], - use_pca=use_pca[0], + self.pca1 = PCA( n_modes=n_pca_modes[0], init_rank_reduction=pca_init_rank_reduction[0], + use_pca=use_pca[0], sample_name=sample_name, feature_name=feature_name[0], ) - self.whitener2 = Whitener( - alpha=alpha[1], - use_pca=use_pca[1], + + self.pca2 = PCA( n_modes=n_pca_modes[1], init_rank_reduction=pca_init_rank_reduction[1], + use_pca=use_pca[1], + sample_name=sample_name, + feature_name=feature_name[1], + ) + + self.whitener1 = Whitener( + alpha=alpha[0], + sample_name=sample_name, + feature_name=feature_name[0], + ) + self.whitener2 = Whitener( + alpha=alpha[1], sample_name=sample_name, feature_name=feature_name[1], ) @@ -294,11 +303,14 @@ def fit( # Preprocess data X = self.preprocessor1.fit_transform(X, self.sample_dims, weights_X) Y = self.preprocessor2.fit_transform(Y, self.sample_dims, weights_Y) + # Perform PCA + X = self.pca1.fit_transform(X) + Y = self.pca2.fit_transform(Y) + # Augment data + X, Y = self._augment_data(X, Y) # Whiten data X = self.whitener1.fit_transform(X) Y = self.whitener2.fit_transform(Y) - # Augment data - X, Y = self._augment_data(X, Y) # Fit the model self._fit_algorithm(X, Y) @@ -334,21 +346,25 @@ def transform( validate_input_type(X) # Preprocess X X = self.preprocessor1.transform(X) + X = self.pca1.transform(X) X = self.whitener1.transform(X) if Y is not None: validate_input_type(Y) # Preprocess Y Y = self.preprocessor2.transform(Y) + Y = self.pca2.transform(Y) Y = self.whitener2.transform(Y) data = self._transform_algorithm(X, Y, normalized=normalized) data_list = [] if X is not None: X = self.whitener1.inverse_transform_scores_unseen(data["X"]) + X = self.pca1.inverse_transform_scores_unseen(X) X = self.preprocessor1.inverse_transform_scores_unseen(X) data_list.append(X) if Y is not None: Y = self.whitener2.inverse_transform_scores_unseen(data["Y"]) + Y = self.pca2.inverse_transform_scores_unseen(Y) Y = self.preprocessor2.inverse_transform_scores_unseen(Y) data_list.append(Y) @@ -397,11 +413,13 @@ def inverse_transform( if x_is_given: X = inv_transformed["X"] X = self.whitener1.inverse_transform_data(X) + X = self.pca1.inverse_transform_data(X) Xrec = self.preprocessor1.inverse_transform_data(X) results.append(Xrec) if y_is_given: Y = inv_transformed["Y"] Y = self.whitener2.inverse_transform_data(Y) + Y = self.pca2.inverse_transform_data(Y) Yrec = self.preprocessor2.inverse_transform_data(Y) results.append(Yrec) @@ -429,6 +447,7 @@ def predict(self, X: DataObject) -> DataArray: # Preprocess X X = self.preprocessor1.transform(X) + X = self.pca1.transform(X) # Whiten X X = self.whitener1.transform(X) @@ -438,6 +457,7 @@ def predict(self, X: DataObject) -> DataArray: # Inverse transform Y Y = self.whitener2.inverse_transform_scores_unseen(Y) + Y = self.pca2.inverse_transform_scores_unseen(Y) Y = self.preprocessor2.inverse_transform_scores_unseen(Y) return Y @@ -465,6 +485,9 @@ def components(self, normalized=True) -> tuple[DataObject, DataObject]: Px = self.whitener1.inverse_transform_components(Px) Py = self.whitener2.inverse_transform_components(Py) + Px = self.pca1.inverse_transform_components(Px) + Py = self.pca2.inverse_transform_components(Py) + Px: DataObject = self.preprocessor1.inverse_transform_components(Px) Py: DataObject = self.preprocessor2.inverse_transform_components(Py) return Px, Py @@ -492,6 +515,9 @@ def scores(self, normalized=False) -> tuple[DataArray, DataArray]: Rx = self.whitener1.inverse_transform_scores(Rx) Ry = self.whitener2.inverse_transform_scores(Ry) + Rx = self.pca1.inverse_transform_scores(Rx) + Ry = self.pca2.inverse_transform_scores(Ry) + Rx: DataArray = self.preprocessor1.inverse_transform_scores(Rx) Ry: DataArray = self.preprocessor2.inverse_transform_scores(Ry) return Rx, Ry @@ -509,6 +535,8 @@ def get_serialization_attrs(self) -> dict: data=self.data, preprocessor1=self.preprocessor1, preprocessor2=self.preprocessor2, + pca1=self.pca1, + pca2=self.pca2, whitener1=self.whitener1, whitener2=self.whitener2, sample_name=self.sample_name, diff --git a/xeofs/cross/cpcca.py b/xeofs/cross/cpcca.py index ad88808..d6cf60f 100644 --- a/xeofs/cross/cpcca.py +++ b/xeofs/cross/cpcca.py @@ -453,8 +453,8 @@ def _compute_residual_variance_numpy(X, Y, Xrec, Yrec): X2 = self.data["input_data2"] # Unwhiten the data - X1 = self.whitener1.inverse_transform_data(X1, unwhiten_only=True) - X2 = self.whitener2.inverse_transform_data(X2, unwhiten_only=True) + X1 = self.whitener1.inverse_transform_data(X1) + X2 = self.whitener2.inverse_transform_data(X2) # Rename the sample dimension to avoid conflicts for # different coordinates with same length @@ -477,8 +477,8 @@ def _compute_residual_variance_numpy(X, Y, Xrec, Yrec): ) # Unwhitend the reconstructed data - X1r = self.whitener1.inverse_transform_data(X1r, unwhiten_only=True) - X2r = self.whitener2.inverse_transform_data(X2r, unwhiten_only=True) + X1r = self.whitener1.inverse_transform_data(X1r) + X2r = self.whitener2.inverse_transform_data(X2r) # Compute fraction variance explained X1r = X1r.rename({self.sample_name: sample_name_x}) @@ -536,7 +536,7 @@ def fraction_variance_X_explained_by_X(self): X = self.data["input_data1"] # Unwhiten the data - X = self.whitener1.inverse_transform_data(X, unwhiten_only=True) + X = self.whitener1.inverse_transform_data(X) # Compute the total variance total_variance: DataArray = self._compute_total_variance(X, self.sample_name) @@ -551,7 +551,7 @@ def fraction_variance_X_explained_by_X(self): Xr = xr.dot(Rx.sel(mode=[mode]), Qx.sel(mode=[mode]).conj().T, dims="mode") # Unwhitend the reconstructed data - Xr = self.whitener1.inverse_transform_data(Xr, unwhiten_only=True) + Xr = self.whitener1.inverse_transform_data(Xr) # Compute fraction variance explained residual_variance = self._compute_total_variance(X - Xr, self.sample_name) @@ -588,7 +588,7 @@ def fraction_variance_Y_explained_by_Y(self): Y = self.data["input_data2"] # Unwhiten the data - Y = self.whitener2.inverse_transform_data(Y, unwhiten_only=True) + Y = self.whitener2.inverse_transform_data(Y) # Compute the total variance total_variance: DataArray = self._compute_total_variance(Y, self.sample_name) @@ -603,7 +603,7 @@ def fraction_variance_Y_explained_by_Y(self): Yr = xr.dot(Ry.sel(mode=[mode]), Qy.sel(mode=[mode]).conj().T, dims="mode") # Unwhitend the reconstructed data - Yr = self.whitener2.inverse_transform_data(Yr, unwhiten_only=True) + Yr = self.whitener2.inverse_transform_data(Yr) # Compute fraction variance explained residual_variance = self._compute_total_variance(Y - Yr, self.sample_name) @@ -660,8 +660,8 @@ def _compute_residual_variance_numpy(X, Y, Xrec, Yrec): X2 = self.data["input_data2"] # Unwhiten the data - X1 = self.whitener1.inverse_transform_data(X1, unwhiten_only=True) - X2 = self.whitener2.inverse_transform_data(X2, unwhiten_only=True) + X1 = self.whitener1.inverse_transform_data(X1) + X2 = self.whitener2.inverse_transform_data(X2) # Compute the total variance X1 = X1.rename({self.sample_name: sample_name_x}) @@ -694,8 +694,8 @@ def _compute_residual_variance_numpy(X, Y, Xrec, Yrec): ) # Unwhitend the reconstructed data - X1r = self.whitener1.inverse_transform_data(X1r, unwhiten_only=True) - X2r = self.whitener2.inverse_transform_data(X2r, unwhiten_only=True) + X1r = self.whitener1.inverse_transform_data(X1r) + X2r = self.whitener2.inverse_transform_data(X2r) # Compute fraction variance explained X1r = X1r.rename({self.sample_name: sample_name_x}) @@ -770,6 +770,9 @@ def homogeneous_patterns(self, correction=None, alpha=0.05): input_data1 = self.whitener1.inverse_transform_data(input_data1) input_data2 = self.whitener2.inverse_transform_data(input_data2) + input_data1 = self.pca1.inverse_transform_data(input_data1) + input_data2 = self.pca2.inverse_transform_data(input_data2) + scores1 = self.data["scores1"] scores2 = self.data["scores2"] @@ -851,6 +854,9 @@ def heterogeneous_patterns(self, correction=None, alpha=0.05): input_data1 = self.whitener1.inverse_transform_data(input_data1) input_data2 = self.whitener2.inverse_transform_data(input_data2) + input_data1 = self.pca1.inverse_transform_data(input_data1) + input_data2 = self.pca2.inverse_transform_data(input_data2) + scores1 = self.data["scores1"] scores2 = self.data["scores2"] @@ -982,8 +988,8 @@ def _compute_total_squared_covariance(self, C: DataArray) -> DataArray: Requires the unwhitened covariance matrix which we can obtain by multiplying the whitened covariance matrix with the inverse of the whitening transformation matrix. """ - C = self.whitener2.inverse_transform_data(C, unwhiten_only=True) - C = self.whitener1.inverse_transform_data(C.conj().T, unwhiten_only=True) + C = self.whitener2.inverse_transform_data(C) + C = self.whitener1.inverse_transform_data(C.conj().T) # Not necessary to conjugate transpose for total squared covariance # C = C.conj().T return (abs(C) ** 2).sum() @@ -1183,6 +1189,9 @@ def components_amplitude(self, normalized=True) -> tuple[DataObject, DataObject] Px = self.whitener1.inverse_transform_components(Px) Py = self.whitener2.inverse_transform_components(Py) + Px = self.pca1.inverse_transform_components(Px) + Py = self.pca2.inverse_transform_components(Py) + Px = abs(Px) Py = abs(Py) @@ -1218,6 +1227,9 @@ def components_phase(self, normalized=True) -> tuple[DataObject, DataObject]: Px = self.whitener1.inverse_transform_components(Px) Py = self.whitener2.inverse_transform_components(Py) + Px = self.pca1.inverse_transform_components(Px) + Py = self.pca2.inverse_transform_components(Py) + Px = xr.apply_ufunc(np.angle, Px, keep_attrs=True, dask="allowed") Py = xr.apply_ufunc(np.angle, Py, keep_attrs=True, dask="allowed") @@ -1253,6 +1265,9 @@ def scores_amplitude(self, normalized=False) -> tuple[DataArray, DataArray]: Rx = self.whitener1.inverse_transform_scores(Rx) Ry = self.whitener2.inverse_transform_scores(Ry) + Rx = self.pca1.inverse_transform_scores(Rx) + Ry = self.pca2.inverse_transform_scores(Ry) + Rx = abs(Rx) Ry = abs(Ry) @@ -1288,6 +1303,9 @@ def scores_phase(self, normalized=False) -> tuple[DataArray, DataArray]: Rx = self.whitener1.inverse_transform_scores(Rx) Ry = self.whitener2.inverse_transform_scores(Ry) + Rx = self.pca1.inverse_transform_scores(Rx) + Ry = self.pca2.inverse_transform_scores(Ry) + Rx = xr.apply_ufunc(np.angle, Rx, keep_attrs=True, dask="allowed") Ry = xr.apply_ufunc(np.angle, Ry, keep_attrs=True, dask="allowed") diff --git a/xeofs/cross/cpcca_rotator.py b/xeofs/cross/cpcca_rotator.py index 218759c..b83b915 100644 --- a/xeofs/cross/cpcca_rotator.py +++ b/xeofs/cross/cpcca_rotator.py @@ -7,7 +7,7 @@ from ..base_model import BaseModel from ..data_container import DataContainer from ..linalg.rotation import promax -from ..preprocessing import Preprocessor, Whitener +from ..preprocessing import PCA, Preprocessor, Whitener from ..utils.data_types import DataArray, DataObject from ..utils.xarray_utils import argsort_dask, get_deterministic_sign_multiplier from .cpcca import CPCCA, ComplexCPCCA, HilbertCPCCA @@ -95,6 +95,8 @@ def __init__( # Attach empty objects self.preprocessor1 = Preprocessor() self.preprocessor2 = Preprocessor() + self.pca1 = PCA() + self.pca2 = PCA() self.whitener1 = Whitener() self.whitener2 = Whitener() self.data = DataContainer() @@ -108,6 +110,8 @@ def get_serialization_attrs(self) -> dict: model_data=self.model_data, preprocessor1=self.preprocessor1, preprocessor2=self.preprocessor2, + pca1=self.pca1, + pca2=self.pca2, whitener1=self.whitener1, whitener2=self.whitener2, sorted=self.sorted, @@ -118,6 +122,8 @@ def get_serialization_attrs(self) -> dict: def _fit_algorithm(self, model) -> Self: self.preprocessor1 = model.preprocessor1 self.preprocessor2 = model.preprocessor2 + self.pca1 = model.pca1 + self.pca2 = model.pca2 self.whitener1 = model.whitener1 self.whitener2 = model.whitener2 self.sample_name = model.sample_name @@ -154,6 +160,8 @@ def _fit_algorithm(self, model) -> Self: # Unwhiten and back-transform into physical space Qx = self.whitener1.inverse_transform_components(Qx) Qy = self.whitener2.inverse_transform_components(Qy) + Qx = self.pca1.inverse_transform_components(Qx) + Qy = self.pca2.inverse_transform_components(Qy) # Rename the feature dimension to a common name so that the combined vectors can be concatenated Qx = Qx.rename({feature_name[0]: common_feature_dim}) @@ -194,6 +202,8 @@ def _fit_algorithm(self, model) -> Self: # For consistency with the unrotated model classes, we transform the pattern vectors # into the whitened PC space + Qx_rot = self.pca1.transform_components(Qx_rot) + Qy_rot = self.pca2.transform_components(Qy_rot) Qx_rot = self.whitener1.transform_components(Qx_rot) Qy_rot = self.whitener2.transform_components(Qy_rot) @@ -353,6 +363,7 @@ def transform( # Preprocess the data comps1 = self.whitener1.inverse_transform_components(comps1) + comps1 = self.pca1.inverse_transform_components(comps1) X = self.preprocessor1.transform(X) # Compute non-rotated scores by projecting the data onto non-rotated components @@ -383,6 +394,7 @@ def transform( # Preprocess the data comps2 = self.whitener2.inverse_transform_components(comps2) + comps2 = self.pca2.inverse_transform_components(comps2) Y = self.preprocessor2.transform(Y) # Compute non-rotated scores by project the data onto non-rotated components diff --git a/xeofs/preprocessing/__init__.py b/xeofs/preprocessing/__init__.py index 826949b..abec6c9 100644 --- a/xeofs/preprocessing/__init__.py +++ b/xeofs/preprocessing/__init__.py @@ -1,6 +1,7 @@ from .concatenator import Concatenator from .dimension_renamer import DimensionRenamer from .multi_index_converter import MultiIndexConverter +from .pca import PCA from .preprocessor import Preprocessor from .sanitizer import Sanitizer from .scaler import Scaler @@ -16,4 +17,5 @@ "Scaler", "Stacker", "Whitener", + "PCA", ] diff --git a/xeofs/preprocessing/pca.py b/xeofs/preprocessing/pca.py new file mode 100644 index 0000000..9de9e3d --- /dev/null +++ b/xeofs/preprocessing/pca.py @@ -0,0 +1,181 @@ +import numpy as np +import xarray as xr +from typing_extensions import Self + +from ..linalg.svd import SVD +from ..utils.data_types import ( + DataArray, + Dims, + DimsList, +) +from ..utils.sanity_checks import assert_single_dataarray +from .transformer import Transformer + + +class PCA(Transformer): + """Transform data into reduced PC space. + + For large number of features, use PCA to reduce the dimensionality of the data before whitening. + + Note that for ``alpha=1`` (no whiteneing) and ``use_pca=False``, it just becomes the identity transformation. + + Parameters + ---------- + n_modes: int | float | str, default="all" + If int, number of components to keep. If float, fraction of variance to keep. If `n_modes="all"`, keep all components. + use_pca: bool, default=True + Whether or not to use PCA to reduce the dimensionality of the data. If False, perform identity transformation. + init_rank_reduction: float, default=0.3 + Used only when `n_modes` is given as a float. Specifiy the initial PCA rank reduction before truncating the solution to the desired fraction of explained variance. Must be in the half open interval ]0, 1]. Lower values will speed up the computation. + compute_eagerly: bool, default=False + Whether to perform eager or lazy computation. + sample_name: str, default="sample" + Name of the sample dimension. + feature_name: str, default="feature" + Name of the feature dimension. + random_state: np.random.Generator | int | None, default=None + Random seed for reproducibility. + solver_kwargs: dict + Additional keyword arguments for the SVD solver. + + """ + + def __init__( + self, + n_modes: int | float | str = "all", + use_pca: bool = True, + init_rank_reduction: float = 0.3, + compute_eagerly: bool = False, + sample_name: str = "sample", + feature_name: str = "feature", + random_state: np.random.Generator | int | None = None, + solver_kwargs: dict = {}, + ): + super().__init__(sample_name, feature_name) + + self.use_pca = use_pca + self.n_modes = n_modes + self.init_rank_reduction = init_rank_reduction + self.compute_eagerly = compute_eagerly + self.random_state = random_state + self.solver_kwargs = solver_kwargs + + # Check whether Whitener is identity transformation + self.is_identity = not use_pca + + def _sanity_check_input(self, X) -> None: + assert_single_dataarray(X) + + if len(X.dims) != 2: + raise ValueError("Input DataArray must have shape 2") + + if X.dims != (self.sample_name, self.feature_name): + raise ValueError( + "Input DataArray must have dimensions ({:}, {:})".format( + self.sample_name, self.feature_name + ) + ) + + def _get_n_modes(self, X: DataArray) -> int | float: + if isinstance(self.n_modes, str): + if self.n_modes == "all": + return min(X.shape) + else: + raise ValueError("`n_modes` must be an integer, float or 'all'") + else: + return self.n_modes + + def get_serialization_attrs(self) -> dict: + return dict( + V=self.V, + use_pca=self.use_pca, + ) + + def fit( + self, + X: DataArray, + sample_dims: Dims | None = None, + feature_dims: DimsList | None = None, + ) -> Self: + self._sanity_check_input(X) + + if self.use_pca: + # In case of "all" modes to the rank of the input data + self.n_modes = self._get_n_modes(X) + + svd = SVD( + n_modes=self.n_modes, + init_rank_reduction=self.init_rank_reduction, + compute=self.compute_eagerly, + random_state=self.random_state, + sample_name=self.sample_name, + feature_name=self.feature_name, + **self.solver_kwargs, + ) + _, _, self.V = svd.fit_transform(X) + + else: + self.V = xr.DataArray(1, name="identity") + + return self + + def transform(self, X: DataArray) -> DataArray: + """Transform new data into the PC space.""" + + self._sanity_check_input(X) + if self.use_pca: + transformed = xr.dot(X, self.V, dims=self.feature_name) + transformed.name = X.name + return transformed.rename({"mode": self.feature_name}) + else: + return X + + def fit_transform( + self, + X: DataArray, + sample_dims: Dims | None = None, + feature_dims: DimsList | None = None, + ) -> DataArray: + return self.fit(X, sample_dims, feature_dims).transform(X) + + def inverse_transform_data(self, X: DataArray) -> DataArray: + """Transform 2D data (sample x feature) from PC space back into original space.""" + if self.use_pca: + X = X.rename({self.feature_name: "mode"}) + return xr.dot(X, self.V.conj().T, dims="mode") + else: + return X + + def transform_components(self, X: DataArray) -> DataArray: + """Transform 2D components (feature x mode) into PC space.""" + + if self.use_pca: + dummy_dim = "dummy_dim" + Tinv = self.V.conj().T + Tinv = Tinv.rename({"mode": dummy_dim}) + transformed = xr.dot(Tinv, X, dims=self.feature_name) + return transformed.rename({dummy_dim: self.feature_name}) + else: + return X + + def inverse_transform_components(self, X: DataArray) -> DataArray: + """Transform 2D components (feature x mode) from PC space back into original space.""" + + if self.use_pca: + dummy_dim = "dummy_dim" + comps_pc_space = X.rename({self.feature_name: dummy_dim}) + V = self.V + V = V.rename({"mode": dummy_dim}) + return xr.dot(V, comps_pc_space, dims=dummy_dim) + else: + return X + + def inverse_transform_scores(self, X: DataArray) -> DataArray: + """Transform 2D scores (sample x mode) from whitened PC space back into original space.""" + + return X + + def inverse_transform_scores_unseen(self, X: DataArray) -> DataArray: + """Transform unseen 2D scores (sample x mode) from whitened PC space back into original space.""" + + return X diff --git a/xeofs/preprocessing/whitener.py b/xeofs/preprocessing/whitener.py index 4bf7660..a10e000 100644 --- a/xeofs/preprocessing/whitener.py +++ b/xeofs/preprocessing/whitener.py @@ -5,7 +5,6 @@ from typing_extensions import Self from ..linalg._numpy import _fractional_matrix_power -from ..linalg.svd import SVD from ..utils.data_types import ( DataArray, Dims, @@ -18,44 +17,26 @@ class Whitener(Transformer): """Fractional whitening of 2D DataArray. - For large number of features, use PCA to reduce the dimensionality of the data before whitening. - - Note that for ``alpha=1`` (no whiteneing) and ``use_pca=False``, it just becomes the identity transformation. + For ``alpha=1`` (no whiteneing), it just becomes the identity transformation. Parameters ---------- alpha: float, default=0 Power parameter to perform fractional whitening, where 0 corresponds to full whitening (standardized and decorrelated) and 1 to no whitening. - use_pca: bool, default=False - If True, perform PCA before whitening to speed up the computation. This is the recommended setting for large number of features. Specify the number of components to keep in `n_modes`. - n_modes: int | float | str, default=None - If int, number of components to keep. If float, fraction of variance to keep. If `n_modes="all"`, keep all components. - init_rank_reduction: float, default=0.3 - Used only when `n_modes` is given as a float. Specifiy the initial PCA rank reduction before truncating the solution to the desired fraction of explained variance. Must be in the half open interval ]0, 1]. Lower values will speed up the computation. - compute_svd: bool, default=False - Whether to perform eager or lazy computation. sample_name: str, default="sample" Name of the sample dimension. feature_name: str, default="feature" Name of the feature dimension. - random_state: np.random.Generator | int | None, default=None + random_state: int | None, default=None Random seed for reproducibility. - solver_kwargs: dict - Additional keyword arguments for the SVD solver. - """ def __init__( self, - alpha: float = 0.0, - use_pca: bool = False, - n_modes: int | float | str = "all", - init_rank_reduction: float = 0.3, - compute_svd: bool = False, + alpha: float = 0, sample_name: str = "sample", feature_name: str = "feature", - random_state: np.random.Generator | int | None = None, - solver_kwargs: dict = {}, + random_state: int | None = None, ): super().__init__(sample_name, feature_name) @@ -66,12 +47,7 @@ def __init__( alpha = float(alpha) self.alpha = alpha - self.use_pca = use_pca - self.n_modes = n_modes - self.init_rank_reduction = init_rank_reduction - self.compute_svd = compute_svd self.random_state = random_state - self.solver_kwargs = solver_kwargs # Check whether Whitener is identity transformation self.is_identity = self._check_identity_transform() @@ -79,7 +55,7 @@ def __init__( def _check_identity_transform(self) -> bool: eps = np.finfo(self.alpha).eps alpha_is_one = (1.0 - self.alpha) < eps - if not self.use_pca and alpha_is_one: + if alpha_is_one: return True else: return False @@ -97,22 +73,10 @@ def _sanity_check_input(self, X) -> None: ) ) - def _get_n_modes(self, X: DataArray) -> int | float: - if isinstance(self.n_modes, str): - if self.n_modes == "all": - return min(X.shape) - else: - raise ValueError("`n_modes` must be an integer, float or 'all'") - else: - return self.n_modes - def get_serialization_attrs(self) -> dict: return dict( alpha=self.alpha, - n_modes=self.n_modes, - use_pca=self.use_pca, is_identity=self.is_identity, - s=self.s, T=self.T, Tinv=self.Tinv, feature_name=self.feature_name, @@ -132,38 +96,15 @@ def fit( if self.is_identity: self.T = xr.DataArray(1, name="identity") self.Tinv = xr.DataArray(1, name="identity") - self.s = xr.DataArray(1, name="identity") + # Compute the fractional whitening transformation directly based on covariance matrix else: - if self.use_pca: - # In case of "all" modes to the rank of the input data - self.n_modes = self._get_n_modes(X) - - svd = SVD( - n_modes=self.n_modes, - init_rank_reduction=self.init_rank_reduction, - compute=self.compute_svd, - random_state=self.random_state, - sample_name=self.sample_name, - feature_name=self.feature_name, - **self.solver_kwargs, + if n_samples < n_features: + warnings.warn( + f"The number of samples ({n_samples}) is smaller than the number of features ({n_features}), leading to an ill-conditioned problem. This may cause unstable results. Consider using PCA to reduce dimensionality and stabilize the problem by setting `use_pca=True`." ) - _, s, V = svd.fit_transform(X) - - n_c: float = np.sqrt(n_samples - 1) - self.T: DataArray = V * (s / n_c) ** (self.alpha - 1) - self.Tinv = (s / n_c) ** (1 - self.alpha) * V.conj().T - self.s = s - - # Without PCA compute the fractional whitening transformation directly based on covariance matrix - else: - if n_samples < n_features: - warnings.warn( - f"The number of samples ({n_samples}) is smaller than the number of features ({n_features}), leading to an ill-conditioned problem. This may cause unstable results. Consider using PCA to reduce dimensionality and stabilize the problem by setting `use_pca=True`." - ) - self.T, self.Tinv = self._compute_whitener_transform(X) - self.s = xr.DataArray(1, name="identity") + self.T, self.Tinv = self._compute_whitener_transform(X) return self @@ -180,36 +121,19 @@ def _compute_whitener_transform(self, X: DataArray) -> tuple[DataArray, DataArra return T, Tinv def _compute_whitener_transform_numpy(self, X): - nc = X.shape[0] - 1 + nc = X.shape[0] C = X.conj().T @ X / nc power = (self.alpha - 1) / 2 - svd_kwargs = {"random_state": self.random_state} + svd_kwargs = {"random_state": self.random_state, "solver": "full"} T = _fractional_matrix_power(C, power, **svd_kwargs) - Tinv = np.linalg.inv(T) + try: + Tinv = np.linalg.inv(T) + except np.linalg.LinAlgError: + Tinv = np.linalg.pinv(T) return T, Tinv - def get_Tinv(self, unwhiten_only=False) -> DataArray: - """Get the inverse transformation to unwhiten the data without PC transform. - - In contrast to `inverse_transform()`, this method returns the inverse transformation matrix without the PC transformation. That is, for PC transormed data this transformation only unwhitens the data without transforming back into the input space. For non-PC transformed data, this transformation is equivalent to the inverse transformation. - """ - if self.use_pca and unwhiten_only: - n_c = np.sqrt(self.n_samples - 1) - Tinv = (self.s / n_c) ** (1 - self.alpha) - Tinv = xr.apply_ufunc( - np.diag, - Tinv, - input_core_dims=[["mode"]], - output_core_dims=[["mode", self.feature_name]], - dask="allowed", - ) - Tinv = Tinv.assign_coords({self.feature_name: self.s.coords["mode"].data}) - return Tinv - else: - return self.Tinv - def transform(self, X: DataArray) -> DataArray: - """Transform new data into the fractional whitened PC space.""" + """Transform new data into the fractional whitened space.""" self._sanity_check_input(X) if self.is_identity: @@ -227,25 +151,22 @@ def fit_transform( ) -> DataArray: return self.fit(X, sample_dims, feature_dims).transform(X) - def inverse_transform_data(self, X: DataArray, unwhiten_only=False) -> DataArray: - """Transform 2D data (sample x feature) from whitened PC space back into original space. + def inverse_transform_data(self, X: DataArray) -> DataArray: + """Transform 2D data (sample x feature) from whitened space back into original space. Parameters ---------- X: DataArray Data to transform back into original space. - unwhiten_only: bool, default=False - If True, only unwhiten the data without transforming back into the input space. This is useful when the data was transformed with PCA before whitening and you need the unwhitened data in the PC space. """ - T_inv = self.get_Tinv(unwhiten_only=unwhiten_only) if self.is_identity: return X else: X = X.rename({self.feature_name: "mode"}) - return xr.dot(X, T_inv, dims="mode") + return xr.dot(X, self.Tinv, dims="mode") def transform_components(self, X: DataArray) -> DataArray: - """Transform 2D components (feature x mode) into whitened PC space.""" + """Transform 2D components (feature x mode) into whitened space.""" if self.is_identity: return X @@ -257,7 +178,7 @@ def transform_components(self, X: DataArray) -> DataArray: return transformed.rename({dummy_dim: self.feature_name}) def inverse_transform_components(self, X: DataArray) -> DataArray: - """Transform 2D components (feature x mode) from whitened PC space back into original space.""" + """Transform 2D components (feature x mode) from whitened space back into original space.""" if self.is_identity: return X @@ -269,11 +190,11 @@ def inverse_transform_components(self, X: DataArray) -> DataArray: return xr.dot(VS, comps_pc_space, dims=dummy_dim) def inverse_transform_scores(self, X: DataArray) -> DataArray: - """Transform 2D scores (sample x mode) from whitened PC space back into original space.""" + """Transform 2D scores (sample x mode) from whitened space back into original space.""" return X def inverse_transform_scores_unseen(self, X: DataArray) -> DataArray: - """Transform unseen 2D scores (sample x mode) from whitened PC space back into original space.""" + """Transform unseen 2D scores (sample x mode) from whitened space back into original space.""" return X diff --git a/xeofs/single/pop.py b/xeofs/single/pop.py index c783a96..6173c72 100644 --- a/xeofs/single/pop.py +++ b/xeofs/single/pop.py @@ -5,7 +5,7 @@ from typing_extensions import Self from ..linalg import total_variance -from ..preprocessing import Whitener +from ..preprocessing import PCA from ..utils.data_types import DataArray, DataObject from ..utils.xarray_utils import argsort_dask from .base_model_single_set import BaseModelSingleSet @@ -133,14 +133,13 @@ def __init__( ) self.attrs.update({"model": "Principal Oscillation Pattern analysis"}) - self.whitener = Whitener( - alpha=1.0, + self.pca = PCA( use_pca=use_pca, n_modes=n_pca_modes, init_rank_reduction=pca_init_rank_reduction, sample_name=sample_name, feature_name=feature_name, - compute_svd=compute, + compute_eagerly=compute, random_state=random_state, solver_kwargs=solver_kwargs, ) @@ -151,7 +150,7 @@ def get_serialization_attrs(self) -> dict: return dict( data=self.data, preprocessor=self.preprocessor, - whitener=self.whitener, + pca=self.pca, sorted=self.sorted, ) @@ -201,7 +200,7 @@ def _fit_algorithm(self, X: DataArray) -> Self: feature_name = self.feature_name # Transform in PC space - X = self.whitener.fit_transform(X) + X = self.pca.fit_transform(X) P, Z, lbda, T, tau = xr.apply_ufunc( self._np_solve_pop_system, @@ -235,7 +234,7 @@ def _fit_algorithm(self, X: DataArray) -> Self: idx_modes_sorted = argsort_dask(norms, "mode")[::-1] # type: ignore idx_modes_sorted.coords.update(norms.coords) - P = self.whitener.inverse_transform_components(P) + P = self.pca.inverse_transform_components(P) # Store the results self.data.add(X, "input_data", allow_compute=False) @@ -274,8 +273,8 @@ def _transform_algorithm(self, X: DataArray) -> DataArray: P = self.data["components"] # Transform into PC spcae - P = self.whitener.transform_components(P) - X = self.whitener.transform(X) + P = self.pca.transform_components(P) + X = self.pca.transform(X) # Project the data Z = xr.apply_ufunc( @@ -288,7 +287,7 @@ def _transform_algorithm(self, X: DataArray) -> DataArray: ) Z.name = "scores" - Z = self.whitener.inverse_transform_scores(Z) + Z = self.pca.inverse_transform_scores(Z) return Z @@ -312,13 +311,13 @@ def _inverse_transform_algorithm(self, scores: DataArray) -> DataArray: P = self.data["components"].sel(mode=scores.mode) # Transform in PC space - P = self.whitener.transform_components(P) + P = self.pca.transform_components(P) reconstructed_data = xr.dot(scores, P, dims="mode") reconstructed_data.name = "reconstructed_data" # Inverse transform the data into physical space - reconstructed_data = self.whitener.inverse_transform_data(reconstructed_data) + reconstructed_data = self.pca.inverse_transform_data(reconstructed_data) return reconstructed_data From dff493bc50f701af80b3ce0ce34f220ab7ae7469 Mon Sep 17 00:00:00 2001 From: Niclas Rieger <45175997+nicrie@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:25:34 +0200 Subject: [PATCH 6/7] docs: fix changelog path (#232) --- .github/workflows/release_package.yml | 4 +- docs/whats_new/CHANGELOG.md | 1111 ------------------------- 2 files changed, 2 insertions(+), 1113 deletions(-) delete mode 100644 docs/whats_new/CHANGELOG.md diff --git a/.github/workflows/release_package.yml b/.github/workflows/release_package.yml index dc5c562..67288de 100644 --- a/.github/workflows/release_package.yml +++ b/.github/workflows/release_package.yml @@ -52,7 +52,7 @@ jobs: config: cliff.toml args: --verbose env: - OUTPUT: docs/whats_new/CHANGELOG.md + OUTPUT: docs/content/whats_new/CHANGELOG.md GITHUB_REPO: ${{ github.repository }} - name: Commit Changelog @@ -60,6 +60,6 @@ jobs: git config user.name 'github-actions[bot]' git config user.email 'github-actions[bot]@users.noreply.github.com' set +e - git add docs/whats_new/CHANGELOG.md + git add docs/content/whats_new/CHANGELOG.md git commit -m "docs(changelog): update changelog" git push https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git main diff --git a/docs/whats_new/CHANGELOG.md b/docs/whats_new/CHANGELOG.md deleted file mode 100644 index c4aca6a..0000000 --- a/docs/whats_new/CHANGELOG.md +++ /dev/null @@ -1,1111 +0,0 @@ -# What's New? - -All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines. - ---- -## [3.0.1](https://github.com/nicrie/xeofs/compare/v3.0.0..v3.0.1) - 2024-09-10 - -### 🐛 Bug Fixes - -- **(cross)** fix data augmentation in Hilbert cross-set models (#222) - ([960f744](https://github.com/nicrie/xeofs/commit/960f74429a01d614bd9289d762550128206330a0)) - Niclas Rieger -- **(cross)** compute total squared covariance in PC space (#221) - ([2dd3e86](https://github.com/nicrie/xeofs/commit/2dd3e86d1b41416346a31c6dffd261834990426b)) - Niclas Rieger - -### 🔖 Documentation - -- **(changelog)** update changelog - ([800e608](https://github.com/nicrie/xeofs/commit/800e6086d3f7032f8e9917fd3c4ff2f118f9e84e)) - github-actions[bot] -- **(changelog)** update changelog - ([75bbe7a](https://github.com/nicrie/xeofs/commit/75bbe7af4a9eb072d660c9e331d0284410910e55)) - github-actions[bot] -- add migration guide (#217) - ([dfaab53](https://github.com/nicrie/xeofs/commit/dfaab53734f33042d6762aba4799afa5479585c4)) - Niclas Rieger - ---- -## [3.0.0](https://github.com/nicrie/xeofs/compare/v2.4.1..v3.0.0) - 2024-09-04 - -### ⭐ Features - -- [**BREAKING**] Support for Complex and Hilbert models (#202) - ([77ac921](https://github.com/nicrie/xeofs/commit/77ac921cbbbec8db91143d40a144ef65eab1098c)) - Niclas Rieger -- add Continuum Power CCA (#196) - ([de9eaa7](https://github.com/nicrie/xeofs/commit/de9eaa7819c9237d2b8011e6940baa6511d1cd51)) - Niclas Rieger -- [**BREAKING**] remove verbose parameter (#205) - ([2fd90dd](https://github.com/nicrie/xeofs/commit/2fd90dd06dd2ca919c21fed6eb53a14aee61a5bb)) - Niclas Rieger -- change default value of parameter `normalized` (#207) - ([278d2ee](https://github.com/nicrie/xeofs/commit/278d2ee28e9366631537c78de5a4778ae6c76b0d)) - Niclas Rieger -- add CCA as cross-set analysis (#211) - ([fcba987](https://github.com/nicrie/xeofs/commit/fcba987d513674bccdf5b25226eb2a5c9a9d83e2)) - Niclas Rieger -- add Redundancy Analysis (#212) - ([76c4605](https://github.com/nicrie/xeofs/commit/76c4605917213af33cd030b730589f5c1b4daa95)) - Niclas Rieger -- add POP analysis (#215) - ([5a0cc95](https://github.com/nicrie/xeofs/commit/5a0cc95346963a73b1a44f61ce1d2e097867edbd)) - Niclas Rieger - -### 🐛 Bug Fixes - -- standardize variable names to `X` and `Y` for consistency (#206) - ([a1ebe58](https://github.com/nicrie/xeofs/commit/a1ebe582ef8c4714f864d67c3ab5d64ecd2f596b)) - Niclas Rieger -- alpha paramter in Hilbert CCA (#213) - ([dba4478](https://github.com/nicrie/xeofs/commit/dba44787e6cb25c61301cdc7031e0acab9c72854)) - Niclas Rieger - -### 🔖 Documentation - -- **(changelog)** update changelog - ([be6664d](https://github.com/nicrie/xeofs/commit/be6664d08c49e5f588bc44bcd8f1852d4bea2c43)) - github-actions[bot] -- **(changelog)** update changelog - ([a4e1be2](https://github.com/nicrie/xeofs/commit/a4e1be220ed572e6bd8490a0e1a7f4e1ad6ac34d)) - github-actions[bot] -- **(readme)** fix broken badges (#204) - ([72e8c60](https://github.com/nicrie/xeofs/commit/72e8c60941bd00a091e5d6d27635622a26aa9bcf)) - Niclas Rieger - -### 🛠️ Refactoring - -- **(typing)** use built-in types for annotations (#208) - ([7505350](https://github.com/nicrie/xeofs/commit/75053507a2a4128d25e2c46fe24ef0f85a07364a)) - Niclas Rieger -- [**BREAKING**] reorganize methods into new namespaces (#210) - ([675983f](https://github.com/nicrie/xeofs/commit/675983fbb030380e6bde5ce7ede02346d8c9debe)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- merge branch 'main' into develop - ([8acc4ae](https://github.com/nicrie/xeofs/commit/8acc4ae213ce2d91a3ce78fb07dabfff180e0f9f)) - Niclas Rieger - ---- -## [2.4.1](https://github.com/nicrie/xeofs/compare/v2.4.0..v2.4.1) - 2024-08-31 - -### 🐛 Bug Fixes - -- add deprecation warning for `Complex` model classes (#201) - ([6bac230](https://github.com/nicrie/xeofs/commit/6bac230423b33169d6e32adaf3e885277d83773b)) - Niclas Rieger - -### 🔖 Documentation - -- **(changelog)** move log to documentation (#199) - ([c9de931](https://github.com/nicrie/xeofs/commit/c9de931327faae972747e3b0d3fbfded4c3b9a91)) - Niclas Rieger -- **(changelog)** update changelog - ([280bd43](https://github.com/nicrie/xeofs/commit/280bd430383ae5fecc47275f1964945b1bd5479b)) - github-actions[bot] -- **(changelog)** update changelog - ([8eed958](https://github.com/nicrie/xeofs/commit/8eed958bbc23dc2d3a5ae8195f02e1b8efb813e5)) - github-actions[bot] -- **(decomposer)** improve error messages (#194) - ([9d34060](https://github.com/nicrie/xeofs/commit/9d34060617a45c60b02bf504e38d7b202285ccfc)) - Niclas Rieger -- reorganize documentation (#171) - ([aa28770](https://github.com/nicrie/xeofs/commit/aa287706f590c7afe7eeb6d51c30d64b4d285cc0)) - Niclas Rieger -- update color design (#181) - ([25598a1](https://github.com/nicrie/xeofs/commit/25598a1fc23e41560e9d85327f7c52c0420825f4)) - Niclas Rieger -- add favicon (#182) - ([33e5bbb](https://github.com/nicrie/xeofs/commit/33e5bbb11e847d944424de410afef04d642ac2b6)) - Niclas Rieger -- add logo for social preview (#190) - ([ae6a900](https://github.com/nicrie/xeofs/commit/ae6a90083193613571e3955e6bd419c490111ff4)) - Niclas Rieger -- move CHANGELOG to docs directory - ([541b749](https://github.com/nicrie/xeofs/commit/541b749d73ac6d8d2bd6f12ddd25419af9b29787)) - Niclas Rieger -- add myst-parser package - ([24d7692](https://github.com/nicrie/xeofs/commit/24d7692149085c453d19b6dc3da3f22697cc5c31)) - Niclas Rieger -- add CHANGELOG into documentation - ([9188ef2](https://github.com/nicrie/xeofs/commit/9188ef280c174d7dc14df5e7282a2efb01567771)) - Niclas Rieger -- remove icons from changelog - ([153b777](https://github.com/nicrie/xeofs/commit/153b77772957f75b10bbe1685496f4db0722f423)) - Niclas Rieger -- add icons as unicode - ([b8586aa](https://github.com/nicrie/xeofs/commit/b8586aa6376a1ddfa490c43576548cedfddc8c06)) - Niclas Rieger -- fix typo - ([bd7f429](https://github.com/nicrie/xeofs/commit/bd7f4297ad9005bc05025fe2edfc9469d1f2e54e)) - Niclas Rieger - -### 🛠️ Refactoring - -- **(datacontainer)** parameter list serialization (#193) - ([7b9764d](https://github.com/nicrie/xeofs/commit/7b9764d65c1e0900bfd4d4d2b744e8d69bb2831a)) - Niclas Rieger -- **(decomposer)** allow truncated SVD based on variance (#184) - ([5973c41](https://github.com/nicrie/xeofs/commit/5973c41e7acab3c999747dcc89503f260ad68451)) - Niclas Rieger -- **(sanitizer)** reuse utility function to test input types (#192) - ([db03601](https://github.com/nicrie/xeofs/commit/db0360188fcaac8cd9df57d77f3105ab9edd77d8)) - Niclas Rieger -- **(test)** clean preprocessing test suite (#180) - ([95e216e](https://github.com/nicrie/xeofs/commit/95e216ea35206f489de4e60dc5d1873f82ea9e8c)) - Niclas Rieger -- **(whitener)** add Whitener to whiten a 2D matrix (#185) - ([a6b61b3](https://github.com/nicrie/xeofs/commit/a6b61b31ba14a67e1f3d6fd52ca5625ad362b1cc)) - Niclas Rieger -- **(whitener)** add more flexibility (#191) - ([65ab502](https://github.com/nicrie/xeofs/commit/65ab5028461145441424ced7098f3cc4b544cc2c)) - Niclas Rieger -- add `PCAPreprocessor` class (#186) - ([9003182](https://github.com/nicrie/xeofs/commit/90031824a27f406c96ca26a86b7f941edefaf815)) - Niclas Rieger -- merge branch 'main' into develop - ([1f38f48](https://github.com/nicrie/xeofs/commit/1f38f48043a42ddbadcb4ac4bec9168b39ba4f43)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- **(changelog)** remove old Github Action - ([45b513e](https://github.com/nicrie/xeofs/commit/45b513e57e3f32aa8498b94f5b5e4c84ac61ecfe)) - Niclas Rieger -- update Github Issue templates (#172) - ([998774f](https://github.com/nicrie/xeofs/commit/998774fe50259ec2341aa1b21da51a9f077df22c)) - Niclas Rieger -- merge main into develop - ([4e683e6](https://github.com/nicrie/xeofs/commit/4e683e644be0975e3f2e388703a5dd88484da674)) - Niclas Rieger -- updating internal code structure (#195) - ([00d5fb7](https://github.com/nicrie/xeofs/commit/00d5fb7dcd176aa6eaf274805ebfc29eb937c8b6)) - Niclas Rieger -- update git-cliff Github Action - ([f8b3305](https://github.com/nicrie/xeofs/commit/f8b33053bd2c2bd09808887b890a1d2fab611eb3)) - Niclas Rieger -- add deprecation warning for verbose parameter - ([87b23f9](https://github.com/nicrie/xeofs/commit/87b23f9d8dfc219730dda22800c5ec98206dc9d3)) - Niclas Rieger -- add deprecation warning for verbose parameter (#200) - ([f2f3354](https://github.com/nicrie/xeofs/commit/f2f335415aa49d575ce08e5a983a466b076fa0c0)) - Niclas Rieger - ---- -## [2.4.0](https://github.com/nicrie/xeofs/compare/v2.3.3..v2.4.0) - 2024-07-18 - -### ⭐ Features - -- add sparse PCA using variable projection (#170) - ([e2ccc76](https://github.com/nicrie/xeofs/commit/e2ccc7621c33f55a1ca4c2afe588123ea12deffa)) - Niclas Rieger - ---- -## [2.3.3](https://github.com/nicrie/xeofs/compare/v2.3.2..v2.3.3) - 2024-07-10 - -### 🐛 Bug Fixes - -- support new datatree and numpy 2.0 (#169) - ([536709c](https://github.com/nicrie/xeofs/commit/536709ce171ca5c07ca9a83b06cec3bb6a1eded7)) - Sam Levang - -### 🔖 Documentation - -- new changelog (#141) - ([35b9ef7](https://github.com/nicrie/xeofs/commit/35b9ef7dee37b16dce109325b9e4374a422612bc)) - Sam Levang -- Remove release commits from CHANGELOG (#163) - ([31bc358](https://github.com/nicrie/xeofs/commit/31bc35855514682e1a8ddfd68f88d1a89db6fcfe)) - Niclas Rieger -- Add How to Cite page (#165) - ([388f87c](https://github.com/nicrie/xeofs/commit/388f87c7fb97a450b11f060e9d19dffe46b4fb6b)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- use `uv` and `ruff` (#160) - ([a9ba9ff](https://github.com/nicrie/xeofs/commit/a9ba9ffa0bd8aa3368b2a895cfbfd2b18c57db09)) - Sam Levang -- install build in semantic release (#161) - ([185dba8](https://github.com/nicrie/xeofs/commit/185dba8799390ab97b128e23f18befa3983809cb)) - Sam Levang - ---- -## [2.3.2](https://github.com/nicrie/xeofs/compare/v2.3.1..v2.3.2) - 2024-03-30 - -### 🐛 Bug Fixes - -- handle indexing changes in new xarray versions (#159) - ([7d07d30](https://github.com/nicrie/xeofs/commit/7d07d30690523b3ba844a5e0b388cdeb9eb1ce02)) - Sam Levang - ---- -## [2.3.1](https://github.com/nicrie/xeofs/compare/v2.3.0..v2.3.1) - 2024-02-28 - -### 🐛 Bug Fixes - -- correct inverse transform of unseen scores (#154) - ([5f0f7a5](https://github.com/nicrie/xeofs/commit/5f0f7a5593da5ce266b1e951f0d632cfe51b311c)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- constrain xarray version (#155) - ([8158657](https://github.com/nicrie/xeofs/commit/8158657fd837407c64e01969317aea39d1f192d7)) - Niclas Rieger - ---- -## [2.3.0](https://github.com/nicrie/xeofs/compare/v2.2.6..v2.3.0) - 2024-02-04 - -### ⭐ Features - -- support complex input data (#152) - ([c6ce4e1](https://github.com/nicrie/xeofs/commit/c6ce4e171399f774f7d49dd40b59fa5477b38e8d)) - Niclas Rieger - ---- -## [2.2.6](https://github.com/nicrie/xeofs/compare/v2.2.5..v2.2.6) - 2024-02-04 - -### 🐛 Bug Fixes - -- inverse_transform fails with single mode and normalized pcs (#151) - ([9ea7547](https://github.com/nicrie/xeofs/commit/9ea7547c5f5b60722f675b1dab776a507da9b9e1)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- Merge branch 'main' into develop - ([5758238](https://github.com/nicrie/xeofs/commit/5758238109483a3ab33fa8be78100714ea91f34d)) - Niclas Rieger - ---- -## [2.2.5](https://github.com/nicrie/xeofs/compare/v2.2.4..v2.2.5) - 2024-01-28 - -### 🐛 Bug Fixes - -- incorrect explained variance in CCA (#147) - ([f825c68](https://github.com/nicrie/xeofs/commit/f825c68c885a14d546165f95ea2ca5ff7f5c6f4b)) - Niclas Rieger - -### 🔖 Documentation - -- add CITATION.cff (#144) - ([787c598](https://github.com/nicrie/xeofs/commit/787c5982111eaa046e697a63aaa7b71180452314)) - Niclas Rieger -- update xeofs citation (#145) - ([4cf84cf](https://github.com/nicrie/xeofs/commit/4cf84cfc0c99e790e53b183370ca9edfdbdd01c5)) - Niclas Rieger - ---- -## [2.2.4](https://github.com/nicrie/xeofs/compare/v2.2.3..v2.2.4) - 2023-12-23 - -### 🐛 Bug Fixes - -- bump new release for JOSS (#143) - ([c3e4780](https://github.com/nicrie/xeofs/commit/c3e4780e915c2c72ecdf21739742b5b8a5ad07bd)) - Niclas Rieger - -### 🔖 Documentation - -- use doctest to test README examples (#135) - ([382f6c7](https://github.com/nicrie/xeofs/commit/382f6c7db5496d6a8921e3044add4acc8acb75d5)) - Niclas Rieger -- automatically sync quickstart tutorial with codebase (#136) - ([48e63a0](https://github.com/nicrie/xeofs/commit/48e63a09579ff559908183ea2f13032886d64290)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- loose version constraints of dependencies (#137) - ([1a0efe1](https://github.com/nicrie/xeofs/commit/1a0efe1cbd18d5ce444a6e42eb2f290c9619dcb0)) - Niclas Rieger - ---- -## [2.2.3](https://github.com/nicrie/xeofs/compare/v2.2.2..v2.2.3) - 2023-12-18 - -### 🐛 Bug Fixes - -- handle preprocessor serialization for dataset - ([5bcdf5c](https://github.com/nicrie/xeofs/commit/5bcdf5c95cdf2d0cf4206c386268b4ffd0fed053)) - Sam Levang -- test serialization roundtrip with dataset - ([ff9a1be](https://github.com/nicrie/xeofs/commit/ff9a1bea1441e0c742ca049750352b110b6376b1)) - Sam Levang -- serialization for dataset (#139) - ([93fce38](https://github.com/nicrie/xeofs/commit/93fce38ca170b1c6c9adb32b345a4531f2db6fdf)) - Niclas Rieger - -### 🔖 Documentation - -- show contributors in README - ([049e654](https://github.com/nicrie/xeofs/commit/049e6544637d4c396d40ffcb1b29b7a710c584de)) - Sam Levang -- Update link to contributing guide (#129) - ([21b8d3d](https://github.com/nicrie/xeofs/commit/21b8d3dd3cf46fb631f7c2fe28fdf95246cd4153)) - Niclas Rieger -- fix link to perf scripts (#131) - ([e2b3276](https://github.com/nicrie/xeofs/commit/e2b32762bd6bb3111c973bbd571b56367a496eba)) - Niclas Rieger -- add required dependencies - ([ce6b93a](https://github.com/nicrie/xeofs/commit/ce6b93ab31da6ab10ebc70e9444f1b9956ec02b2)) - Niclas Rieger -- add required dependencies (#138) - ([7ec694f](https://github.com/nicrie/xeofs/commit/7ec694f41d6aca0509782096cbabbcd19264180a)) - Niclas Rieger -- remove all-contributor config file - ([f5234fc](https://github.com/nicrie/xeofs/commit/f5234fc3895f3bcd8893ae684ea23a0ebe001db4)) - Niclas Rieger -- show contributors in readme (#140) - ([0c12fa5](https://github.com/nicrie/xeofs/commit/0c12fa5385e807e2cd45599e6fc0f10b3a520119)) - Niclas Rieger - ---- -## [2.2.2](https://github.com/nicrie/xeofs/compare/v2.2.1..v2.2.2) - 2023-11-20 - -### 🐛 Bug Fixes - -- trigger new release - ([c79a116](https://github.com/nicrie/xeofs/commit/c79a1160d5b27e44f82b9c8fe9a9ac808ffca895)) - Niclas Rieger - ---- -## [2.2.1](https://github.com/nicrie/xeofs/compare/v2.2.0..v2.2.1) - 2023-11-20 - -### 🐛 Bug Fixes - -- trigger new release - ([58250b9](https://github.com/nicrie/xeofs/commit/58250b948bf1d2316a589b3872a44d3d7ecb7ec9)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- add password input for PyPI token - ([600b43e](https://github.com/nicrie/xeofs/commit/600b43ec432ff58ab0c0eb27799a0726801fcd1b)) - Niclas Rieger - ---- -## [2.2.0](https://github.com/nicrie/xeofs/compare/v2.1.2..v2.2.0) - 2023-11-20 - -### ⭐ Features - -- support saving to netcdf (#123) - ([8d06153](https://github.com/nicrie/xeofs/commit/8d06153b5de31aad3f0817979c1122250f444c57)) - Sam Levang -- normalized option in inverse_transform - ([e7d6d90](https://github.com/nicrie/xeofs/commit/e7d6d90d6fdcbd4575cddf3b12ebf1852dc329ef)) - Sam Levang -- normalized option in inverse_transform (#124) - ([4ad2791](https://github.com/nicrie/xeofs/commit/4ad2791d4db672c7c722ee2e5d12cf3b3a88028c)) - Sam Levang - -### 🐛 Bug Fixes - -- test loaded model vs transform not scores - ([a9d6bdf](https://github.com/nicrie/xeofs/commit/a9d6bdf21278477bb42d7a406aed6053dee9c223)) - Sam Levang -- update to handle v8 semantic release GH action (#127) - ([5b8c6d8](https://github.com/nicrie/xeofs/commit/5b8c6d83f10d9439b0ced8d72c06c1fb36804fe4)) - Sam Levang - -### 🔖 Documentation - -- **(CHANGELOG)** update release notes - ([42b5c5e](https://github.com/nicrie/xeofs/commit/42b5c5ebb1e98c20a98c3ab770eb94000243b6d2)) - Niclas Rieger -- **(changelog)** use PAT instead of GITHUB_TOKEN - ([fa58532](https://github.com/nicrie/xeofs/commit/fa5853203b514146b295d9185e436295fed18c81)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- merge branch 'main' into develop - ([95a34f6](https://github.com/nicrie/xeofs/commit/95a34f60f9ea3ea6706423b2343de2990d80dbf6)) - Niclas Rieger -- add workflow dispatch to Github Action - ([26f8345](https://github.com/nicrie/xeofs/commit/26f834516d1aacecdffc91e425a801c2086bdcd8)) - Niclas Rieger -- specify Python Semantic Release version v8.0.0 - ([c7c79e4](https://github.com/nicrie/xeofs/commit/c7c79e448462d96d49cec2ab903363085e5ed9d4)) - Niclas Rieger - ---- -## [2.1.2](https://github.com/nicrie/xeofs/compare/v2.1.1..v2.1.2) - 2023-11-18 - -### 🐛 Bug Fixes - -- update default power iterations for improved accuracy (#122) - ([9045bc6](https://github.com/nicrie/xeofs/commit/9045bc68f18b34df2ff88c64de9131d8d26e3e38)) - Niclas Rieger - -### 🔖 Documentation - -- reactivate CHANGELOG - ([06a2014](https://github.com/nicrie/xeofs/commit/06a201463a0cbce258cc6bde27e7fcab316980b7)) - Niclas Rieger -- reactivate CHANGELOG (#121) - ([6a4370b](https://github.com/nicrie/xeofs/commit/6a4370b0bdfa31a679ce97b1aa7f1ab56041dbe4)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- Merge branch 'main' into develop - ([607d25b](https://github.com/nicrie/xeofs/commit/607d25bd827f849dbd40c5fb0b68ca93a95e45a5)) - Niclas Rieger - ---- -## [2.1.1](https://github.com/nicrie/xeofs/compare/v2.1.0..v2.1.1) - 2023-11-17 - -### 🐛 Bug Fixes - -- only sort transform if model is already sorted (#118) - ([dfe0001](https://github.com/nicrie/xeofs/commit/dfe0001a18a2ebd2073831e2b6484ace86ea0088)) - Sam Levang - -### 🔖 Documentation - -- acknowledge contributors in README (#115) - ([f2781d8](https://github.com/nicrie/xeofs/commit/f2781d88371f9a772b67b3458dd098d2281920a5)) - Niclas Rieger -- acknowledge contributors in README (#116) - ([1a88379](https://github.com/nicrie/xeofs/commit/1a8837915bf89b83c58de1cdff4fe826b0f6d43d)) - Niclas Rieger -- fix broken link - ([de49038](https://github.com/nicrie/xeofs/commit/de4903863b7c9fd53f8bae39ff6505bcdfadeb6e)) - Niclas Rieger - -### 🛠️ Refactoring - -- set computed attrs via deserialize (#117) - ([1878406](https://github.com/nicrie/xeofs/commit/1878406702963f099c8a0b3ba2db494d4ef0bab4)) - Sam Levang - -### ⚙️ Miscellaneous - -- merge branches - ([4447b07](https://github.com/nicrie/xeofs/commit/4447b07a948bc4cdd083ef781c605a630e29c6dc)) - Niclas Rieger -- bring all-contributors bot back to life - ([8929ed5](https://github.com/nicrie/xeofs/commit/8929ed5a1f6757eb9f55e191eb4d7d417ad37b1b)) - Niclas Rieger -- fix config of all-contributor bot - ([1eedf4f](https://github.com/nicrie/xeofs/commit/1eedf4f5c2b69f30ee45be0c110d9cca7e25ce9d)) - Niclas Rieger -- merge branch 'main' into develop - ([f9ef729](https://github.com/nicrie/xeofs/commit/f9ef72920becf750f63bc8ed4f8b80f6c25593e4)) - Niclas Rieger - ---- -## [2.1.0](https://github.com/nicrie/xeofs/compare/v2.0.3..v2.1.0) - 2023-11-14 - -### ⭐ Features - -- add EEOF inverse_transform() (#111) - ([ab6e3cc](https://github.com/nicrie/xeofs/commit/ab6e3cced44f288c48905bda34e2e4e6d1b60dee)) - Niclas Rieger -- lazy execution mode (#110) - ([314901e](https://github.com/nicrie/xeofs/commit/314901ebd978290d56214219d7a87f5111f07775)) - Sam Levang - -### 🔖 Documentation - -- remove dead files - ([6c4842e](https://github.com/nicrie/xeofs/commit/6c4842e34d83aaa9dce06e41c32665a21611a928)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- merge branches - ([c36eca5](https://github.com/nicrie/xeofs/commit/c36eca57505df28b18f7853e8a880199025fef37)) - Niclas Rieger - ---- -## [2.0.3](https://github.com/nicrie/xeofs/compare/v2.0.2..v2.0.3) - 2023-11-12 - -### 🐛 Bug Fixes - -- remove poetry io group (#113) - ([23cca05](https://github.com/nicrie/xeofs/commit/23cca0582748ecaa8df34adfdc7be26ed5a513e1)) - Niclas Rieger - ---- -## [2.0.2](https://github.com/nicrie/xeofs/compare/v2.0.1..v2.0.2) - 2023-11-08 - -### 🐛 Bug Fixes - -- safe default zarr write mode - ([0f95d04](https://github.com/nicrie/xeofs/commit/0f95d0444a6e9d93dba30bd0c4211fe9000ea6bd)) - Sam Levang -- safe default zarr write mode (#107) - ([3003f5a](https://github.com/nicrie/xeofs/commit/3003f5a104db3c954e7e9ad48f88e7056e8fa5bb)) - Niclas Rieger - ---- -## [2.0.1](https://github.com/nicrie/xeofs/compare/v2.0.0..v2.0.1) - 2023-11-07 - -### 🐛 Bug Fixes - -- circumvent pypi block with v2.0.1 release - ([d014c87](https://github.com/nicrie/xeofs/commit/d014c87b6f604f9a5d014ee93c09e2b7f9f31f92)) - Niclas Rieger -- circumvent pypi block with v2.0.1 release (#105) - ([dbad76a](https://github.com/nicrie/xeofs/commit/dbad76a3f17967b18b30acb53a2e56e488606f0f)) - Niclas Rieger - ---- -## [2.0.0](https://github.com/nicrie/xeofs/compare/v1.2.2..v2.0.0) - 2023-11-07 - -### ⭐ Features - -- [**BREAKING**] inverse_transform scores arg - ([7c1dc16](https://github.com/nicrie/xeofs/commit/7c1dc16515f3c7588324eba53be9327ca8b4e8c2)) - Sam Levang -- [**BREAKING**] inverse transform for unseen data (#102) - ([4974844](https://github.com/nicrie/xeofs/commit/4974844bbd5fcfb16806f0d393505d4b93805140)) - Niclas Rieger -- serialization methods (#103) - ([69df4d9](https://github.com/nicrie/xeofs/commit/69df4d9378197935793fc2863f3ffa6a716af49b)) - Sam Levang - -### 🔖 Documentation - -- update authors and description - ([3e39eda](https://github.com/nicrie/xeofs/commit/3e39edaa22e9e28ff2140a414af9d1fb82194d67)) - Niclas Rieger -- update authors and description (#100) - ([49c4367](https://github.com/nicrie/xeofs/commit/49c4367b145a84ef4cdbd71f51ef34a63accc654)) - Niclas Rieger - -### ✅ Tests - -- set random_seed for deterministic output - ([9f0058a](https://github.com/nicrie/xeofs/commit/9f0058a63f4294f549476b93c33ee35dcf004ab8)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- Merge branch 'main' into develop - ([0edb852](https://github.com/nicrie/xeofs/commit/0edb852259a171397541cb518c25ad6ca9d20fc2)) - Niclas Rieger - ---- -## [1.2.2](https://github.com/nicrie/xeofs/compare/v1.2.1..v1.2.2) - 2023-10-31 - -### 🚀 Performance - -- **(Dask)** avoid double computation - ([f24cb09](https://github.com/nicrie/xeofs/commit/f24cb09c30d91bd1c2b7405cfdbbfd74a3ead5eb)) - Niclas Rieger -- **(Decomposer)** compute by default - ([1f3b214](https://github.com/nicrie/xeofs/commit/1f3b2146574d56c0724fd194433037ee08428264)) - Niclas Rieger - -### 🐛 Bug Fixes - -- reindexing transformed scores - ([a8e458e](https://github.com/nicrie/xeofs/commit/a8e458e21a5b30c323b93263ff6e7dd687cb391c)) - Niclas Rieger - -### 🔖 Documentation - -- add performance tests - ([42d8e75](https://github.com/nicrie/xeofs/commit/42d8e752de7d052a4f7e2b83eb17a534f1d3789c)) - Niclas Rieger - ---- -## [1.2.1](https://github.com/nicrie/xeofs/compare/v1.2.0..v1.2.1) - 2023-10-29 - -### 🐛 Bug Fixes - -- **(Bootstrapper)** correct scaling of PCs - ([ed59dd9](https://github.com/nicrie/xeofs/commit/ed59dd9a6b66ae878c1eb596a2fa9fb9b0e137ca)) - Niclas Rieger -- parameter to show dask progress bar - ([3482fa4](https://github.com/nicrie/xeofs/commit/3482fa465da66a8e540d10e3a13ef3b8c1461e70)) - Niclas Rieger - -### 🔖 Documentation - -- fix broken example - ([b58d985](https://github.com/nicrie/xeofs/commit/b58d9857b15a599063f99578e8f8d481b77fce9b)) - Niclas Rieger -- fix broken example - ([ecc114e](https://github.com/nicrie/xeofs/commit/ecc114e789ec3711cab6c53d42cf9b67035d3c06)) - Niclas Rieger -- fix up minor errors - ([ef3fe8a](https://github.com/nicrie/xeofs/commit/ef3fe8aa2f707377efdd444b136c29acc592a418)) - Niclas Rieger -- fix reference hyperlinks - ([38cadb9](https://github.com/nicrie/xeofs/commit/38cadb9aa970640f02734457fb07e5e43212facf)) - Niclas Rieger -- update some docstrings - ([2795c54](https://github.com/nicrie/xeofs/commit/2795c54951b47ff0e124566338d5dfa0f8a15b91)) - Niclas Rieger -- repair gallery - ([fa647d8](https://github.com/nicrie/xeofs/commit/fa647d8a0420e2938cea885956bb26a5376cfdaf)) - Niclas Rieger -- update env - ([e8f72d5](https://github.com/nicrie/xeofs/commit/e8f72d5cc97393a806a276ff8932103392435b33)) - Niclas Rieger -- add CoC - ([70f10b2](https://github.com/nicrie/xeofs/commit/70f10b29e3ac22b49bb3de0f00992761c1e6a059)) - Niclas Rieger -- complete overhaul - ([47e13ea](https://github.com/nicrie/xeofs/commit/47e13ea6d0972f67b3e362473380ec802598e409)) - Niclas Rieger -- update examples (#93) - ([9f3c0c1](https://github.com/nicrie/xeofs/commit/9f3c0c15a3e6df57adeabdbc213928a524795fe9)) - Niclas Rieger -- update doc environment (#94) - ([9592dbc](https://github.com/nicrie/xeofs/commit/9592dbcab5b59236b60ce5c82fa2f374e4dc5bec)) - Niclas Rieger -- build with Python 3.11 - ([c181972](https://github.com/nicrie/xeofs/commit/c181972253d9e6bc3ddcfd945f14bc1f06d7ef5f)) - Niclas Rieger -- minor tweaks in documentation - ([977a89e](https://github.com/nicrie/xeofs/commit/977a89e99b74d7629a6478356ffc458ea1c0359f)) - Niclas Rieger -- fix link - ([c6a595b](https://github.com/nicrie/xeofs/commit/c6a595b2d03855b8267992b7f2b345fb1578fe15)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- add ipykernel as dependency - ([066dc2a](https://github.com/nicrie/xeofs/commit/066dc2ac526fc2cd8663507c31c90bd3c692169b)) - Niclas Rieger -- docs require rpy2 package - ([edd69e2](https://github.com/nicrie/xeofs/commit/edd69e2fb5dd15cb83eadb2d15fd9e576962382b)) - Niclas Rieger -- update env - ([572cead](https://github.com/nicrie/xeofs/commit/572cead3c0e4e121de014cfa2115e829457d62c8)) - Niclas Rieger -- add pre-commit as dev dependency - ([3219562](https://github.com/nicrie/xeofs/commit/3219562d225f5c5cf6bb1719bbd2c15689c4ea11)) - Niclas Rieger -- add ipykernel as dependency (#92) - ([7fb4b62](https://github.com/nicrie/xeofs/commit/7fb4b629caf10185372201aae8a954dd3dedbb05)) - Niclas Rieger -- Merge branch 'main' into develop - ([b08e2c3](https://github.com/nicrie/xeofs/commit/b08e2c355ce39c4c28f0e59d0e3a8753a567c82d)) - Niclas Rieger -- no support for 3.12 - ([a0ca2ac](https://github.com/nicrie/xeofs/commit/a0ca2ac706cea2a6453b6a1fb17d054d9d10c5dc)) - Niclas Rieger -- install dev dependencies - ([be8bffd](https://github.com/nicrie/xeofs/commit/be8bffdd746a3a8683a42cfebccac107ff16db60)) - Niclas Rieger -- declare dev docs as optional groups - ([52070d3](https://github.com/nicrie/xeofs/commit/52070d34d9fe67e71a8efed28058823d3de80b43)) - Niclas Rieger - ---- -## [1.2.0](https://github.com/nicrie/xeofs/compare/v1.1.0..v1.2.0) - 2023-10-24 - -### ⭐ Features - -- add GWPCA support - ([58c2dbe](https://github.com/nicrie/xeofs/commit/58c2dbedea9e72f62a80e11415338aa2862e778b)) - Niclas Rieger -- parameter normalized scores - ([f0dcb34](https://github.com/nicrie/xeofs/commit/f0dcb3411f36d8aac29a7b781556afd31a36a6b6)) - Niclas Rieger -- add Extended EOF Analysis - ([2db736c](https://github.com/nicrie/xeofs/commit/2db736cdc5b860c36dbc23c43ffa04251e813533)) - Niclas Rieger -- provide standard kwarg for random_state - ([cf8b641](https://github.com/nicrie/xeofs/commit/cf8b641fffa92892fb7168b0e20dcdd7275cc736)) - Niclas Rieger - -### 🚀 Performance - -- **(dask)** compute SVD result immediately - ([807b7e8](https://github.com/nicrie/xeofs/commit/807b7e896990c4cfb788478644e7647ab3a9ffa1)) - Niclas Rieger - -### 🐛 Bug Fixes - -- **(Bootstrapper)** avoid pertubating sample coords - ([ffd11fc](https://github.com/nicrie/xeofs/commit/ffd11fcf21041b7defba6adb9973807d39678678)) - Niclas Rieger -- **(CCA)** add checks for edge cases - ([76c0b1b](https://github.com/nicrie/xeofs/commit/76c0b1bbe7f64d16b340ae10789f3df3234321b0)) - Niclas Rieger -- **(GWPCA)** raise error in scores - ([43e9274](https://github.com/nicrie/xeofs/commit/43e9274a3eb270a6e331e2f73f77a785e3eb6654)) - Niclas Rieger -- score coordinates in T-mode EOF analysis - ([731eec2](https://github.com/nicrie/xeofs/commit/731eec2825dbe091634cf41a52f38187cce3109c)) - Niclas Rieger -- streamline model attribute types - ([98c5cef](https://github.com/nicrie/xeofs/commit/98c5cef82e542daa7cf7f003c52666bbedba994b)) - Niclas Rieger - -### 🔖 Documentation - -- provide top-level type hints - ([6be8923](https://github.com/nicrie/xeofs/commit/6be892354a208a95d7fe56ebbd3ca90ec86dc31b)) - Niclas Rieger - -### 🛠️ Refactoring - -- **(BaseModel)** create algorithm methods - ([12cfc97](https://github.com/nicrie/xeofs/commit/12cfc975c6203942670f3d8f3bb38f796d02701a)) - Niclas Rieger -- **(BaseModel)** move input check to utils - ([183779a](https://github.com/nicrie/xeofs/commit/183779a188e68550f886d08648075bc9d9ba106b)) - Niclas Rieger -- **(GenericListTransformer)** remove inherit - ([2307169](https://github.com/nicrie/xeofs/commit/2307169a58d42357cca9edb2a60de82a17c95718)) - Niclas Rieger -- **(Preprocessor)** enforce input as list - ([ebadaa2](https://github.com/nicrie/xeofs/commit/ebadaa21fc0d212034ac617f42ccd3b5e579c54b)) - Niclas Rieger -- create hilbert_transform.py - ([166e957](https://github.com/nicrie/xeofs/commit/166e9578e5f08dc383de7a64688d73833f1ccaaa)) - Niclas Rieger -- simplify model data structure - ([09415a3](https://github.com/nicrie/xeofs/commit/09415a3591dc926838c2436c2a94a94adaa37e22)) - Niclas Rieger - -### 🎨 Style - -- **(decomposer)** use structural pattern matching - ([6f8a9cf](https://github.com/nicrie/xeofs/commit/6f8a9cf192e7e660aa4bec112b624ceccea8df93)) - Niclas Rieger -- update typings - ([324c06b](https://github.com/nicrie/xeofs/commit/324c06bba0fdc8afdecc4898c2a40173b01ac9e5)) - Niclas Rieger - -### ✅ Tests - -- **(EOF)** fix random seed - ([4080143](https://github.com/nicrie/xeofs/commit/40801431873acc87f851e88090dc0d919884bfc1)) - Niclas Rieger -- isolated NaNs are invalid test cases - ([d4b5a77](https://github.com/nicrie/xeofs/commit/d4b5a77bc91b7c13426d424dad72eb94a4f1c30f)) - Niclas Rieger -- remove cca-zoo test - ([6ed06f0](https://github.com/nicrie/xeofs/commit/6ed06f0654a2a8b1f44f57ea54159f6b142a04ae)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- Merge branch 'main' into cca - ([a7df1b8](https://github.com/nicrie/xeofs/commit/a7df1b8fec3f13c66befad0a4610705e1aba2bf0)) - Niclas Rieger -- py3.10 requires typing_extension for Self - ([10faa03](https://github.com/nicrie/xeofs/commit/10faa036da1555b5fadbd714ccd529f4849bb2b9)) - Niclas Rieger -- add cca-zoo as dev dependency - ([47e6909](https://github.com/nicrie/xeofs/commit/47e6909f88fc15ae1891ac5b15d76020c179299b)) - Niclas Rieger -- add typing-extensions - ([fb85abe](https://github.com/nicrie/xeofs/commit/fb85abea09d98975d1784fd206a00bdfcd5c5e6f)) - Niclas Rieger -- Release v1.2.0 (#91) - ([d8fc3f3](https://github.com/nicrie/xeofs/commit/d8fc3f3c3de595141f2f4df9258ea70f48351408)) - Niclas Rieger - ---- -## [1.1.0](https://github.com/nicrie/xeofs/compare/v1.0.5..v1.1.0) - 2023-08-31 - -### ⭐ Features - -- **(MCA)** provide optional PCA preprocessing - ([100f40d](https://github.com/nicrie/xeofs/commit/100f40d7d2f5d9c59725948f4a642a511c4b347f)) - Niclas Rieger -- choose the SVD solver - ([50c2386](https://github.com/nicrie/xeofs/commit/50c23864dd0eb3cd18a4113edd87980622f31da9)) - Niclas Rieger -- add Canonical Correlation Analysis - ([782e624](https://github.com/nicrie/xeofs/commit/782e6242ac1f0dd34ed12ee3d0e8154c66551d83)) - Niclas Rieger -- add Optimal Persistence Analysis (OPA) - ([2482110](https://github.com/nicrie/xeofs/commit/248211086e140b5f0d8ec40a40962ed4b56a7b94)) - Niclas Rieger -- choose name of sample and feature dim - ([4d521f8](https://github.com/nicrie/xeofs/commit/4d521f83dea46c558202fc9ce9cfe35c5126453c)) - Niclas Rieger -- add CCA support - ([79e82cc](https://github.com/nicrie/xeofs/commit/79e82ccb0802079123d3254c0ef9a617a962fb90)) - Niclas Rieger - -### 🐛 Bug Fixes - -- PCA preprocessing before Hilbert transform - ([2bbcb69](https://github.com/nicrie/xeofs/commit/2bbcb6929ab92d051623c1b15f7ae0eb2f5eebac)) - Niclas Rieger - -### 🔖 Documentation - -- add Black badge to README - ([759b011](https://github.com/nicrie/xeofs/commit/759b01101da8bbd8e39327d49d24574137fc6bed)) - Niclas Rieger -- update badges - ([51113e4](https://github.com/nicrie/xeofs/commit/51113e4acebfe954b93b8bd79a07c9f2f7a1284a)) - Niclas Rieger -- update location of Action workflow for badge - ([878c5f5](https://github.com/nicrie/xeofs/commit/878c5f58e36e7a87b88800fbcd7229bdec7c0b5a)) - Niclas Rieger -- update location of Action workflow for badge - ([650a8c8](https://github.com/nicrie/xeofs/commit/650a8c8e2ae1738a89e308bf79d3a2c5dd4b7250)) - Niclas Rieger -- add quickstart example and point to methods - ([f741199](https://github.com/nicrie/xeofs/commit/f7411991f29370703560e605578db76b850a2ebc)) - Niclas Rieger -- correct typo in README - ([3b88d5e](https://github.com/nicrie/xeofs/commit/3b88d5e1b28249efc0cbe5f9c86586eedb6273fb)) - Niclas Rieger -- more instructive quickstart (#75) - ([58c2f09](https://github.com/nicrie/xeofs/commit/58c2f09e3e7e57c4d2457445634d125b13c6fda8)) - Niclas Rieger - -### 🛠️ Refactoring - -- **(decomposer)** provide kwargs - ([a75c805](https://github.com/nicrie/xeofs/commit/a75c805d9bf606c8f80d0ce2a7f3112f83287444)) - Niclas Rieger -- **(decomposer)** generalize SVD decomposer - ([8161e6f](https://github.com/nicrie/xeofs/commit/8161e6f0983ecabfc7a65588b672718a81df0a77)) - Niclas Rieger -- **(decomposer)** remove CrossDecomposer - ([52ac245](https://github.com/nicrie/xeofs/commit/52ac2455a1daee40812db38fef4a90906a8626c2)) - Niclas Rieger -- **(decomposer)** coercing signs is optional - ([bb8a0da](https://github.com/nicrie/xeofs/commit/bb8a0dad086bea00ba77c981eee64aa06a18f95e)) - Niclas Rieger -- **(stacker)** allow sample feature DataArray - ([25c2bed](https://github.com/nicrie/xeofs/commit/25c2bedf694307d773f39f8cd2cd27eb311fe105)) - Niclas Rieger -- extra class for renaming (multi)indeces - ([80a4c95](https://github.com/nicrie/xeofs/commit/80a4c95851a9c7d56a3f3fed0073d0c3d5e23578)) - Niclas Rieger -- add class for MultiIndex convertion - ([0033359](https://github.com/nicrie/xeofs/commit/0033359697c9955a33276af599794aab0b85482d)) - Niclas Rieger -- remove dim checks - ([79b43cf](https://github.com/nicrie/xeofs/commit/79b43cf874905c531fe0974a0255d95905101eda)) - Niclas Rieger -- generalize hilbert transform over dims - ([983e0ad](https://github.com/nicrie/xeofs/commit/983e0adf7cf46dcea148d2f24a31b650054f4570)) - Niclas Rieger -- add MultIndexConverter to preprocessor - ([39ee3fb](https://github.com/nicrie/xeofs/commit/39ee3fbc77b1386f92586e699cffe3aa54ad17cd)) - Niclas Rieger -- add MultiIndexConvert & tests - ([52935a0](https://github.com/nicrie/xeofs/commit/52935a0c1e09484046276bedd102ea92d65690d8)) - Niclas Rieger -- add Sanitizer & tests - ([508d742](https://github.com/nicrie/xeofs/commit/508d742dc026b97f28be7af20ac41b12357a4f08)) - Niclas Rieger -- Stacker focuses on stacking - ([9f39f8f](https://github.com/nicrie/xeofs/commit/9f39f8fccacb03ea4e1f1f32f2082a0a525d7589)) - Niclas Rieger -- Sanitizer removes NaNs - ([7b5068b](https://github.com/nicrie/xeofs/commit/7b5068b9215351aaca855d716cc1b7b60069251c)) - Niclas Rieger -- streamline Scaler - ([a62fc5a](https://github.com/nicrie/xeofs/commit/a62fc5a2a617c4c5b7915c182944df6583e6b3ac)) - Niclas Rieger -- adapt Preprocessor to refactoring - ([4a43ce9](https://github.com/nicrie/xeofs/commit/4a43ce98ba37eaf2330ee1956686d3d73a8f2d1b)) - Niclas Rieger -- reflect refactoring in Factory - ([0ce02f0](https://github.com/nicrie/xeofs/commit/0ce02f015c0981811fa5ce1aee02ad4affb97e86)) - Niclas Rieger -- generalize preprocessing in MCA - ([fe9bd46](https://github.com/nicrie/xeofs/commit/fe9bd46cd0e288fbf8bd217acd675bb3d952f0cb)) - Niclas Rieger - -### 🎨 Style - -- **(tests)** apply Black coding format - ([717f5fe](https://github.com/nicrie/xeofs/commit/717f5fe4fbaeb14595bf853721128e6143870127)) - Niclas Rieger -- **(tests)** streamline test names - ([3a1fb91](https://github.com/nicrie/xeofs/commit/3a1fb918244fda8896949fc4bbf38efa459296a5)) - Niclas Rieger -- apply Black formatting to repo - ([029f6e9](https://github.com/nicrie/xeofs/commit/029f6e9192b5c386c4dfe913a664bc6709197402)) - Niclas Rieger -- integrate Black into repo - ([dd4cd7f](https://github.com/nicrie/xeofs/commit/dd4cd7f44ca84e8b52ae9fd2006954ee2a708c5f)) - Niclas Rieger -- add and streamline type hints - ([b51f36a](https://github.com/nicrie/xeofs/commit/b51f36ac2a4235aebafac972547bf14c02f39c7a)) - Niclas Rieger - -### ✅ Tests - -- **(DatasetStacker)** remove failing tests - ([3a2411e](https://github.com/nicrie/xeofs/commit/3a2411e6310ae4f021e3fbd2f317861d9d7282b6)) - Niclas Rieger -- provide method to create synthetic data - ([a737127](https://github.com/nicrie/xeofs/commit/a737127c75531b52e37634d90efb7635d21c42b4)) - Niclas Rieger -- add more flexible data generation classes - ([15f0194](https://github.com/nicrie/xeofs/commit/15f01947ee430138dad6a301dd04ab84c2e534c4)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- **(black)** specify version and target directory - ([7919090](https://github.com/nicrie/xeofs/commit/7919090682f3bf1ba2c689e86cc802529e390cf7)) - Niclas Rieger -- add black as dev dependency - ([81ccaa8](https://github.com/nicrie/xeofs/commit/81ccaa8c0410a5c2961e88206b69e88ac80211ee)) - Niclas Rieger -- update Github Action workflows - ([25211eb](https://github.com/nicrie/xeofs/commit/25211ebcd0e0a0ec23ce823303107febd9f1079f)) - Niclas Rieger -- cosmetic changes (#72) - ([9f44357](https://github.com/nicrie/xeofs/commit/9f44357c1223b513bdbdfc7f40be68dd0b687938)) - Niclas Rieger - ---- -## [1.0.5](https://github.com/nicrie/xeofs/compare/v1.0.4..v1.0.5) - 2023-07-31 - -### 🚀 Performance - -- use xarray built-in method for variance - ([23af4d8](https://github.com/nicrie/xeofs/commit/23af4d8384d01cfd4ef7098b3ccd22ad8d89612b)) - Niclas Rieger - -### 🐛 Bug Fixes - -- **(ListStacker)** use DA Stacker to unstack scores - ([c6479cf](https://github.com/nicrie/xeofs/commit/c6479cfb2601d966f58348149667775c5d467063)) - Niclas Rieger -- weigh modes with norm before reconstructing - ([13f42e0](https://github.com/nicrie/xeofs/commit/13f42e07d4e2765bc1a88421258a1ea1872c6434)) - Niclas Rieger -- transform of rotator classes - ([3368ed0](https://github.com/nicrie/xeofs/commit/3368ed067d6b87ac8ccfb555c2e1f8da41d77fb0)) - Niclas Rieger -- add missing conj for PCA inverse transform - ([e9d6fe1](https://github.com/nicrie/xeofs/commit/e9d6fe10795317f719062c2dacb21c2d2e9d3af5)) - Niclas Rieger -- inverse_transform enforces real output - ([5e97160](https://github.com/nicrie/xeofs/commit/5e971606781e71e81ee7e90af26a994e73d33781)) - Niclas Rieger -- remove **kwargs from BaseModel classes - ([555d0db](https://github.com/nicrie/xeofs/commit/555d0db92ebf0e855e27364a8494168fc8bab622)) - Niclas Rieger -- MCA covariance (fraction) - ([3e4558d](https://github.com/nicrie/xeofs/commit/3e4558d361fa115aea81a20cf17c4e2b62f03389)) - Niclas Rieger - -### 🔖 Documentation - -- **(bootstrapper)** update to new design pattern - ([d61525b](https://github.com/nicrie/xeofs/commit/d61525bff6681c30236111b443af4c39bbbc8a98)) - Niclas Rieger -- update changelog - ([ace7ac9](https://github.com/nicrie/xeofs/commit/ace7ac9ad101c64c7b041f48d4b3629b4bd747f7)) - Niclas Rieger -- extend and streamline doc strings of methods - ([a70b9d0](https://github.com/nicrie/xeofs/commit/a70b9d04e9ce58fd0bdc7ef74540134c383fea7f)) - Niclas Rieger -- improve documentation - ([ea32e7e](https://github.com/nicrie/xeofs/commit/ea32e7ed5dcff5531c6bdaac18a47b6725e7f105)) - Niclas Rieger -- add a Project description to conf.py - ([0a5cfe3](https://github.com/nicrie/xeofs/commit/0a5cfe3c40bf02ae86de8fcef71de553a5ed1406)) - Niclas Rieger -- use mamba to create doc env - ([610facf](https://github.com/nicrie/xeofs/commit/610facfd01b7d7ca277a38119fb60b91a43c8439)) - Niclas Rieger -- add mamba to RTD config - ([33d040b](https://github.com/nicrie/xeofs/commit/33d040b4a01ee2f55b510e4f0fc02eafa3c48019)) - Niclas Rieger -- remove interactivity - ([6e99b8e](https://github.com/nicrie/xeofs/commit/6e99b8e62831da4eee752e0d2ee0ee48c429f3c3)) - Niclas Rieger - -### 🛠️ Refactoring - -- add DataContainer to model structure - ([fc73ccf](https://github.com/nicrie/xeofs/commit/fc73ccff233957b0a9300e2830d0244658e583ef)) - Niclas Rieger -- align Bootstrapper with design pattern - ([f3e2a10](https://github.com/nicrie/xeofs/commit/f3e2a101bc84f42a87d5fb31bb38680cb6ae402a)) - Niclas Rieger -- use values of modes sorting index - ([3925ed6](https://github.com/nicrie/xeofs/commit/3925ed658e3ffd2c02a9fe8acb4bfa270f598bc8)) - Niclas Rieger -- init DataContainer in models - ([fdd0e78](https://github.com/nicrie/xeofs/commit/fdd0e786e7697b6068a5fea182531cffed910b41)) - Niclas Rieger - -### 🎨 Style - -- remove unnecessary imports - ([29954c0](https://github.com/nicrie/xeofs/commit/29954c015bc386952b4e1ac7a07059826b280146)) - Niclas Rieger -- apply rotation matrix in a consistent way - ([2f5286d](https://github.com/nicrie/xeofs/commit/2f5286d956e2b54dfb4ec8b6c7f2fc0a6038d944)) - Niclas Rieger - -### ✅ Tests - -- **(EOF)** check dimensions of components/scores - ([093cee8](https://github.com/nicrie/xeofs/commit/093cee82d8aacafdec8bd8bb7800979c80fee821)) - Niclas Rieger -- **(MCA)** check dimensions of components/scores - ([a214801](https://github.com/nicrie/xeofs/commit/a214801894cb7fabef59072675d2d1678b733615)) - Niclas Rieger -- add basic test for preprocessor - ([aa840b0](https://github.com/nicrie/xeofs/commit/aa840b076437ec3b8c96a0179d04671ea82f1ca9)) - Niclas Rieger -- test cases for transform, inverse_transform - ([bee9b1f](https://github.com/nicrie/xeofs/commit/bee9b1f1727ff2a2400334491e0236a11a699100)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- update python-semantic-release to v8 - ([0887579](https://github.com/nicrie/xeofs/commit/08875797ba42448f0242fd559608b54407e771cd)) - Niclas Rieger -- internal design (#69) - ([b39bdaf](https://github.com/nicrie/xeofs/commit/b39bdaf89c71bac831bb8683831e773e57d353bc)) - Niclas Rieger -- lock PSR to 7.x - ([b5b94e3](https://github.com/nicrie/xeofs/commit/b5b94e3d33ee376addc395d612d6e21c7b13d613)) - Niclas Rieger -- add cartopy and ipywidgets as dev depends - ([4d4dcd4](https://github.com/nicrie/xeofs/commit/4d4dcd4b2899ddc7af70107b08b9840186368c9f)) - Niclas Rieger -- update poetry lock - ([7edfdc9](https://github.com/nicrie/xeofs/commit/7edfdc92b25b1f0e1f898247827cec3d5c4a5bf5)) - Niclas Rieger -- specify cartopy version - ([ec15384](https://github.com/nicrie/xeofs/commit/ec1538477188e2e1f2e9d2a7a1fb0a9d432c7dac)) - Niclas Rieger -- remove cartopy from dev dependencies - ([c95f658](https://github.com/nicrie/xeofs/commit/c95f658c52262bccae16061bd9f256c837031823)) - Niclas Rieger - ---- -## [1.0.4](https://github.com/nicrie/xeofs/compare/v1.0.3..v1.0.4) - 2023-07-22 - -### 🐛 Bug Fixes - -- issue with dot product in rotation - ([ab28cca](https://github.com/nicrie/xeofs/commit/ab28ccad1e1b9dd777e5fef7a2dbbe4c77ab3bb4)) - Niclas Rieger -- use conjugate for squared total variance - ([597ba77](https://github.com/nicrie/xeofs/commit/597ba77ffe4e364946225679a11c7d4a4e059935)) - Niclas Rieger -- consistent (squared) covariance for MCA - ([8e43d15](https://github.com/nicrie/xeofs/commit/8e43d155c32ff210ff5e059b99aa5022c3718496)) - Niclas Rieger -- correct MCA scores and SCF (#66) - ([f6ecc49](https://github.com/nicrie/xeofs/commit/f6ecc49480807b364780c5b61e1c24f7b1c5b9a5)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- Merge branch 'hotfix-mca-scores' into develop - ([f380aed](https://github.com/nicrie/xeofs/commit/f380aed55b8a530565203b8eb524f9df36ce0dff)) - Niclas Rieger - ---- -## [1.0.3](https://github.com/nicrie/xeofs/compare/v1.0.2..v1.0.3) - 2023-07-10 - -### 🐛 Bug Fixes - -- dummy patch to fix package versioning (#53) - ([89d128d](https://github.com/nicrie/xeofs/commit/89d128d9f3e1bea43242c8521e7f3d43716160cd)) - Niclas Rieger - -### 🔖 Documentation - -- add ipython to env to use syntax highlight - ([6b2d16b](https://github.com/nicrie/xeofs/commit/6b2d16b496abfdca4c69e6af556fdeca78227d63)) - Niclas Rieger - -### 🛠️ Refactoring - -- add Scaler and Stacker factories - ([5cf719f](https://github.com/nicrie/xeofs/commit/5cf719ff21ea4dc3a2d66dad0fcb680cfc500b1b)) - Niclas Rieger -- merge Stacker & Scaler into Preprocessor - ([8c27b84](https://github.com/nicrie/xeofs/commit/8c27b8402f3ded1c4eb072e12227b4a53bd68ca6)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- correct package version - ([9070851](https://github.com/nicrie/xeofs/commit/907085143bdd813668d76b4d765a756a0e7a7262)) - Niclas Rieger - ---- -## [1.0.2](https://github.com/nicrie/xeofs/compare/v1.0.1..v1.0.2) - 2023-07-10 - -### 🐛 Bug Fixes - -- **(method)** remove bias in Hilbert transform after padding - ([8e199ef](https://github.com/nicrie/xeofs/commit/8e199ef290018ff3649532f17cbd43d7c37b3681)) - Niclas Rieger -- **(method)** remove bias in Hilbert transform after padding (#52) - ([f65f54b](https://github.com/nicrie/xeofs/commit/f65f54bf4ebc2dda2f787cc7cc5c6ce5bba73eb5)) - Niclas Rieger -- correct bias in Hilbert trans when padding - ([c0183df](https://github.com/nicrie/xeofs/commit/c0183df0a238db16fef8affde66b7c86a8a6382d)) - Niclas Rieger -- correct bias in Hilbert trans when padding (#51) - ([ef37dd3](https://github.com/nicrie/xeofs/commit/ef37dd3b4443605e1106e59a5693d2ec7b9a4fe9)) - Niclas Rieger - -### 🔖 Documentation - -- add dev-dependeny for readthedocs (#46) - ([e1e6379](https://github.com/nicrie/xeofs/commit/e1e6379e2da146e7d8422da45e68bf678561d600)) - Niclas Rieger -- fix broken badge - ([9d9b5d8](https://github.com/nicrie/xeofs/commit/9d9b5d889bbef74f67c3a2b9d946c2373e51d725)) - Niclas Rieger -- add comparison to other packages - ([7985585](https://github.com/nicrie/xeofs/commit/7985585b34fd27fd391f8a0d388723e2f639df30)) - Niclas Rieger -- move to pydata sphinx theme - ([9e92920](https://github.com/nicrie/xeofs/commit/9e92920d75114f1525ab59f01e81daf044f3f975)) - Niclas Rieger -- improve documentation (#48) - ([378aae8](https://github.com/nicrie/xeofs/commit/378aae871c15ad19b1e63631d58d8b00bafd65a2)) - Niclas Rieger -- add pydata-sphinx-theme as doc dependency - ([15c5a3e](https://github.com/nicrie/xeofs/commit/15c5a3e98ebdadbbabf014f5034edacc7f01a6da)) - Niclas Rieger -- add pydata-sphinx-theme as doc dependency (#49) - ([976d390](https://github.com/nicrie/xeofs/commit/976d39008c9cb30f1c9870505d66d73c3ab6f01c)) - Niclas Rieger -- set color styling - ([59213dc](https://github.com/nicrie/xeofs/commit/59213dc1818aadd2f1b0f5bab2d1a4071a55ec8a)) - Niclas Rieger -- citation - ([4054e15](https://github.com/nicrie/xeofs/commit/4054e156247519281d8d9a810a5753f2b45542d5)) - Niclas Rieger -- updates examples - ([f31024b](https://github.com/nicrie/xeofs/commit/f31024bbc531c84a3dd963d5319e3e636dc82c74)) - Niclas Rieger -- citation in bibtex format - ([f33f2d5](https://github.com/nicrie/xeofs/commit/f33f2d5a0add42decd41a1187ec6a071721845f5)) - Niclas Rieger -- minor improvements (#50) - ([00ac5b3](https://github.com/nicrie/xeofs/commit/00ac5b3e3776fe0dd63a8104d2768029459a61b4)) - Niclas Rieger - -### 🛠️ Refactoring - -- Merge 'main' of github.com:nicrie/xeofs - ([93fb010](https://github.com/nicrie/xeofs/commit/93fb010103923d839295fce67cb012e0b70d157f)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- Merge branch 'main' into develop - ([bc4e87d](https://github.com/nicrie/xeofs/commit/bc4e87d469807c4b2bb4679556813e2693affe5f)) - Niclas Rieger -- merge branch 'main' of nicrie/xeofs - ([efb5ab9](https://github.com/nicrie/xeofs/commit/efb5ab9c306e9d71a4aab20b6f16ccbfa6a65c56)) - Niclas Rieger -- merge main into develop - ([9f50392](https://github.com/nicrie/xeofs/commit/9f50392d7f99ca6bb7cd13608b630094e0845cb3)) - Niclas Rieger -- revert errorenous upgrade to v2 - ([ca22aa4](https://github.com/nicrie/xeofs/commit/ca22aa420c4809deaa114de18164ce8d0f5fb02b)) - Niclas Rieger -- merge branch 'main' into develop - ([e0e8d93](https://github.com/nicrie/xeofs/commit/e0e8d93f2ca33ebb091f76a3e04ba5c18b5276ed)) - Niclas Rieger - ---- -## [1.0.1](https://github.com/nicrie/xeofs/compare/v1.0.0..v1.0.1) - 2023-07-07 - -### 🐛 Bug Fixes - -- add dependency statsmodels - ([87e7e1d](https://github.com/nicrie/xeofs/commit/87e7e1d89f5d8dd3f7954bb4ebc79d2d41738404)) - Niclas Rieger -- add dask as dependency (#42) - ([2bb2b6b](https://github.com/nicrie/xeofs/commit/2bb2b6b817a457a7a24918914e88675f08e298d6)) - Niclas Rieger -- build and ci (#45) - ([7d1a88b](https://github.com/nicrie/xeofs/commit/7d1a88b1cda8a66d04f3ffa96e1aa5cfe899029b)) - Niclas Rieger - -### 🔖 Documentation - -- add sphinx related packages to env - ([6e07d3b](https://github.com/nicrie/xeofs/commit/6e07d3b3c6797a787a5b10885c2f73ef5c14cdf8)) - Niclas Rieger - -### ✅ Tests - -- fix incorrect MCA output - ([9a22140](https://github.com/nicrie/xeofs/commit/9a221405ba793122c765eada98a14c8828d4e54c)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- merge 'main' of github.com:nicrie/xeofs - ([cfc164b](https://github.com/nicrie/xeofs/commit/cfc164b9182e771ddc785b6ec81de7ec8f8772bd)) - Niclas Rieger -- Merge branch 'main' into develop - ([d1896d6](https://github.com/nicrie/xeofs/commit/d1896d64a100809ae84140cc67172fa3e0da2f27)) - Niclas Rieger -- remove redundant file - ([4699a56](https://github.com/nicrie/xeofs/commit/4699a566d6f44041c2401516db9ba5a38ff1e140)) - Niclas Rieger -- add dask - ([fef6509](https://github.com/nicrie/xeofs/commit/fef6509bb5fce40b4d03aaca9ed2ef6d7566e5d1)) - Niclas Rieger -- remove python 3.8 support - ([2b408e7](https://github.com/nicrie/xeofs/commit/2b408e71ccc5cacc8011c0ac41a5a357d8d2fd09)) - Niclas Rieger -- drop 3.9 support - ([4552fa9](https://github.com/nicrie/xeofs/commit/4552fa95827d1944fbea0ca8ce9b8a9c4b9e866e)) - Niclas Rieger -- Merge develop of nicrie/xeofs into develop - ([f31e56e](https://github.com/nicrie/xeofs/commit/f31e56e49c550f87c6b132951f97ebd561ec5db5)) - Niclas Rieger - ---- -## [1.0.0](https://github.com/nicrie/xeofs/compare/v0.7.2..v1.0.0) - 2023-07-07 - -### ⭐ Features - -- add support for complex EOF - ([6bff6af](https://github.com/nicrie/xeofs/commit/6bff6af12f0202fbce9cf06453ac66e8921d1d5c)) - Niclas Rieger -- EOF class transform, inverse_trans and corr - ([fb71ffe](https://github.com/nicrie/xeofs/commit/fb71ffede30fdfd65b4b812a62340e9f292fbea6)) - Niclas Rieger -- RotatorFactory to access rotator classes - ([90b2db6](https://github.com/nicrie/xeofs/commit/90b2db687314bc1b62aae5c74c0817eeb806203e)) - Niclas Rieger -- add complex MCA - ([13f8bbc](https://github.com/nicrie/xeofs/commit/13f8bbc8b29e82af37ec6793b416a0ca1e2d1aa5)) - Niclas Rieger -- Rotation supports dask input - ([78360cf](https://github.com/nicrie/xeofs/commit/78360cfbc3b237e8791a32b65aca3f0e7b5d7ec7)) - Niclas Rieger -- skeleton of Bootstrapper class - ([4934b31](https://github.com/nicrie/xeofs/commit/4934b31f8ab3d2d35f371f13abedfd5c178775a1)) - Niclas Rieger -- add meta data to model output - ([083a8e0](https://github.com/nicrie/xeofs/commit/083a8e049140bfbec87f354ed7f0504bbb208fd8)) - Niclas Rieger -- complex MCA amplitude and phase - ([55ce3b1](https://github.com/nicrie/xeofs/commit/55ce3b17f2cb77ea2f11e4fe6444f9860ca5920d)) - Niclas Rieger -- [**BREAKING**] v1.0.0 - ([ec70e8a](https://github.com/nicrie/xeofs/commit/ec70e8a9321d0aa1dc0b44ca83be14f441afef18)) - Niclas Rieger - -### 🚀 Performance - -- always compute scaling arrays prior to analy - ([5b810ce](https://github.com/nicrie/xeofs/commit/5b810ce3f7ecddd9fb44d307e600cd472e07d599)) - Niclas Rieger - -### 🐛 Bug Fixes - -- define names of output arrays - ([c826aa8](https://github.com/nicrie/xeofs/commit/c826aa81dbd1bc9c6441982847bff08c4e9cd333)) - Niclas Rieger -- ListStacker correctly unstacks - ([e363357](https://github.com/nicrie/xeofs/commit/e363357851199b08d916b8efae4bac6a56f5c806)) - Niclas Rieger -- reindex data to ensure deterministic output - ([60c382b](https://github.com/nicrie/xeofs/commit/60c382bc181aacb8997d955c225df96a7b3bed11)) - Niclas Rieger -- remove unecessary dimensions in scores - ([63e2204](https://github.com/nicrie/xeofs/commit/63e2204ab3cce1fdf4ae6a1a153a987fde69e5c0)) - Niclas Rieger -- added missing import - ([207af0a](https://github.com/nicrie/xeofs/commit/207af0ab15267eca5fccbbfb5a464ceb4004d56e)) - Niclas Rieger -- complex decomposition not used - ([2086546](https://github.com/nicrie/xeofs/commit/208654683c9071bc3927e8c7dd549a01e409dea3)) - Niclas Rieger -- phase of complex methods returns np.ndarray - ([dfb050d](https://github.com/nicrie/xeofs/commit/dfb050d82b8d12cc137bd51316b220dd1deb93c3)) - Niclas Rieger -- [**BREAKING**] change parameter dims to dim - ([70fe651](https://github.com/nicrie/xeofs/commit/70fe65147f2dab8be7d9bdf08a81fbd36cc45897)) - Niclas Rieger -- [**BREAKING**] n_components instead of n_modes - ([5d282b1](https://github.com/nicrie/xeofs/commit/5d282b1fc83f150113b13d4f736838676e5d9fff)) - Niclas Rieger -- [**BREAKING**] rename n_rot to n_modes - ([5b39cd4](https://github.com/nicrie/xeofs/commit/5b39cd4d565a82185c76f16b248e6aeae78577cc)) - Niclas Rieger -- number of modes to be rotated defaults 10 - ([b13c833](https://github.com/nicrie/xeofs/commit/b13c833bd12241878b218cf62bbdc3121a8034de)) - Niclas Rieger -- supress warning when unstacking coords - ([2f01695](https://github.com/nicrie/xeofs/commit/2f01695eac40bae2519f7dfd7b4d936b4c6647c5)) - Niclas Rieger -- stacker in T-mode - ([2f9be99](https://github.com/nicrie/xeofs/commit/2f9be995f2a73e75c0bf88b86290246effc5989c)) - Niclas Rieger -- Merge 'release-v1.0.0' into bootstrapper - ([e6ea275](https://github.com/nicrie/xeofs/commit/e6ea27536a43ff086c615ed720a03166d20718de)) - Niclas Rieger -- add components and pattern method to MCA - ([849059b](https://github.com/nicrie/xeofs/commit/849059b65d9218753ef886f5790742ea832a504d)) - Niclas Rieger - -### 🔖 Documentation - -- add docstrings - ([0fe6e24](https://github.com/nicrie/xeofs/commit/0fe6e242f9f4bc1067e8b2fb8e2c0eafaaebf2b2)) - Niclas Rieger -- add more docstrings - ([84ebb5a](https://github.com/nicrie/xeofs/commit/84ebb5ac9a4abca9b30c04b9e9089d3c73ce15a7)) - Niclas Rieger -- improve documentation - ([b7c6680](https://github.com/nicrie/xeofs/commit/b7c6680d196b269301b16143626fc0fea15cd038)) - Niclas Rieger - -### 🛠️ Refactoring - -- **(pandas)** [**BREAKING**] drop support for pandas - ([96196e5](https://github.com/nicrie/xeofs/commit/96196e55a3094ae63266b534aa36e4cedf56d03a)) - nicrie -- add Scaler class - ([a202fa5](https://github.com/nicrie/xeofs/commit/a202fa54098383d40310b294b7a973a49112a411)) - Niclas Rieger -- add Scaler class - ([8428471](https://github.com/nicrie/xeofs/commit/8428471714a74829b9fc601310a6b38f5fb00060)) - Niclas Rieger -- add basic Stacker class - ([8d170be](https://github.com/nicrie/xeofs/commit/8d170bea9097e829520092660f1df34269383ea5)) - Niclas Rieger -- add Decomposer class - ([cb76350](https://github.com/nicrie/xeofs/commit/cb76350998d84fb8c28ec47be6266a98cd3951bc)) - Niclas Rieger -- EOF model uses xarray only - ([369f40b](https://github.com/nicrie/xeofs/commit/369f40bd66f6ac1edf4966a798cb76dda577481f)) - Niclas Rieger -- Merge branch 'stacker' into release-v - ([aea74c7](https://github.com/nicrie/xeofs/commit/aea74c7fbc7f93d9dd22a464085cd984d88bb505)) - Niclas Rieger -- add Rotator class - ([58e66d2](https://github.com/nicrie/xeofs/commit/58e66d2c134fb6b2c6742259c25a44c6a9e18058)) - Niclas Rieger -- components as correlations to testing - ([be1aed6](https://github.com/nicrie/xeofs/commit/be1aed69a60b20ce494d2ec4441617a42656f697)) - Niclas Rieger -- add method in tools for computing corr - ([4866357](https://github.com/nicrie/xeofs/commit/48663574b97a4f8b2f451f7a56b2d1fd00b034b1)) - Niclas Rieger -- add CrossDecomposer - ([43c9ca4](https://github.com/nicrie/xeofs/commit/43c9ca4a02ed0278a8b30278627c856ae9b6d89d)) - Niclas Rieger -- clean and move tests - ([68392e3](https://github.com/nicrie/xeofs/commit/68392e3b70e0fe9fb513bd9b64532484f3f8f8e2)) - Niclas Rieger -- clean up - ([0d868c6](https://github.com/nicrie/xeofs/commit/0d868c6c7229a229502b32bdcea0ff54deb54159)) - Niclas Rieger -- reorganize repo - ([d109a2c](https://github.com/nicrie/xeofs/commit/d109a2cc49ffa5a3dfe512ee0bfec925afdbe65f)) - Niclas Rieger -- move standard, complex model in one file - ([83f0aad](https://github.com/nicrie/xeofs/commit/83f0aadb14f0d09d2ca484aa83cc10e6bb16dac2)) - Niclas Rieger -- Stacker.fit to fit_transform - ([e949c57](https://github.com/nicrie/xeofs/commit/e949c575578e3d585ad6c672f4e83819a52d8e25)) - Niclas Rieger -- Merg 'release-v1.0.0' into bootstrapper - ([97f57ff](https://github.com/nicrie/xeofs/commit/97f57ff32416e188cfbaead67a753d6749c7822e)) - Niclas Rieger -- bootstrapper - ([7074942](https://github.com/nicrie/xeofs/commit/707494211b10da188b64c59684d969fead8d3517)) - Niclas Rieger -- version into separate file - ([d51d167](https://github.com/nicrie/xeofs/commit/d51d1672e6b1dcb8706b4f2222d9c69359e9d978)) - Niclas Rieger -- bootstrapper - ([5525ed1](https://github.com/nicrie/xeofs/commit/5525ed1eddb05c2e8ab043e9fe9b22d4aa6103de)) - Niclas Rieger - -### 🎨 Style - -- ignore .vscode files - ([2877f1d](https://github.com/nicrie/xeofs/commit/2877f1d6cf8b2ae6c51d80fa105e837011ae24d4)) - nicrie -- ignore some type hints - ([d03553a](https://github.com/nicrie/xeofs/commit/d03553a75d6c3f2957524fb7fe81ecc363e61ab9)) - Niclas Rieger -- ignore some type hints - ([dfeb5a0](https://github.com/nicrie/xeofs/commit/dfeb5a0d38e9366753983cfded65470ffea45199)) - Niclas Rieger -- ignore some type hints - ([0e3cff2](https://github.com/nicrie/xeofs/commit/0e3cff22b04e11439a9bae7f028fc2aa03c50f1c)) - Niclas Rieger -- streamline arguments - ([4af426c](https://github.com/nicrie/xeofs/commit/4af426cb99d1736645142a4ac4f0325b4add9165)) - Niclas Rieger - -### ✅ Tests - -- add EOF test cases - ([ba95eb5](https://github.com/nicrie/xeofs/commit/ba95eb50da2bb38dd0ab5fd19d31769319babced)) - Niclas Rieger -- add test cases for ComplexEOF - ([7b6b29b](https://github.com/nicrie/xeofs/commit/7b6b29bed1bacea10f24e803b887a15e0e97ab3a)) - Niclas Rieger -- add tests for MCA and Rotator - ([bc6d1c6](https://github.com/nicrie/xeofs/commit/bc6d1c64faebea4302c62cdfbb5d25baad1a4dc4)) - Niclas Rieger -- add more test cases - ([5921eb4](https://github.com/nicrie/xeofs/commit/5921eb402dbbbc97f14dd98725e634cd124df50a)) - Niclas Rieger - -### ⚙️ Miscellaneous - -- update poetry.lock to newer versions - ([1215e57](https://github.com/nicrie/xeofs/commit/1215e574ee7a41dcffd3add13f57b923f60baf68)) - nicrie -- update to Node 16 - ([7373b8a](https://github.com/nicrie/xeofs/commit/7373b8a40725f0e53c28c4736c6f01e2611fca4c)) - nicrie -- add action for semantic pull request - ([2af2456](https://github.com/nicrie/xeofs/commit/2af2456d25318d028845d2e51534e2282c020e84)) - nicrie -- test also against python 3.9, 3.10 and 3.11 - ([29d0ae0](https://github.com/nicrie/xeofs/commit/29d0ae08a21203df04696cf7ac438fbee9daf691)) - Niclas Rieger -- update environment to Python 3.11 - ([5947194](https://github.com/nicrie/xeofs/commit/5947194733243b3fb84d7abdfb83df1c4fa3dd77)) - Niclas Rieger -- update to most recent versions - ([3a06022](https://github.com/nicrie/xeofs/commit/3a060228456f21dc847c19234bc793b43d76ae99)) - Niclas Rieger -- add sphinx theme - ([5b61bcd](https://github.com/nicrie/xeofs/commit/5b61bcd870bcdd69f5344ae23c4586c68dbfd71c)) - Niclas Rieger -- python 3.10 bug - ([0dd2a18](https://github.com/nicrie/xeofs/commit/0dd2a185e7f51f6d9f70afcb863c392c37fde965)) - Niclas Rieger -- adapt dev versions - ([9dba9c4](https://github.com/nicrie/xeofs/commit/9dba9c48b058bee4669b13cb321d8f057c64d4e4)) - Niclas Rieger -- Update semantic_release.yml - ([7f1b2b4](https://github.com/nicrie/xeofs/commit/7f1b2b40f9918742ef30e48eab9713518f068b1f)) - Niclas Rieger -- do not run on PR - ([9829517](https://github.com/nicrie/xeofs/commit/9829517d2277c0d186552e014777726fdbd03b55)) - Niclas Rieger - ---- -## [0.7.2](https://github.com/nicrie/xeofs/compare/v0.7.1..v0.7.2) - 2023-01-10 - -### 🐛 Bug Fixes - -- FutureWarning in coslat check (#37) - ([285fe0f](https://github.com/nicrie/xeofs/commit/285fe0f6f6cb69cd84e3ac4c662c64d6d659ef47)) - Sam Levang - ---- -## [0.7.1](https://github.com/nicrie/xeofs/compare/v0.7.0..v0.7.1) - 2023-01-08 - -### 🐛 Bug Fixes - -- allow newer xarray version - ([49723c0](https://github.com/nicrie/xeofs/commit/49723c0771b87b8f4b812572f51d50f71bb139e3)) - Sam Levang - ---- -## [0.7.0](https://github.com/nicrie/xeofs/compare/v0.6.0..v0.7.0) - 2022-08-26 - -### ⭐ Features - -- add Rotator class for MCA - ([6adf45f](https://github.com/nicrie/xeofs/commit/6adf45fe0d4a126726c503ab45469f3e488b4890)) - nicrie -- Add Rotator class for MCA - ([0e9e8f9](https://github.com/nicrie/xeofs/commit/0e9e8f90f00d499a385956742fc99ca0776bed83)) - nicrie -- add ROCK PCA - ([0ba0660](https://github.com/nicrie/xeofs/commit/0ba0660fa4f2396dc537888c80be5352dedaebc4)) - nicrie -- Merge branch 'develop' into rock-pca - ([6a5bda8](https://github.com/nicrie/xeofs/commit/6a5bda8ab1fdc3e0c8c2395172385e058c0b7d3d)) - nicrie -- add support for ROCK-PCA - ([202844d](https://github.com/nicrie/xeofs/commit/202844d0e12565bdefb39988a374c4aa20681a0d)) - nicrie - -### 🐛 Bug Fixes - -- numpy and pandas classes did not consider axis parameter - ([8b75271](https://github.com/nicrie/xeofs/commit/8b75271be096107f8a670f97ea6afe2d4e9740a9)) - nicrie -- add stabilizer for communalities during rotation - ([462f2fe](https://github.com/nicrie/xeofs/commit/462f2fe9b30959076a815f3236b48d94c4467f32)) - nicrie - -### 🔖 Documentation - -- fix docs - ([19bb84e](https://github.com/nicrie/xeofs/commit/19bb84e3c57c4762fb2d61b3a60df143e6c05b72)) - nicrie -- more text - ([0f9c32e](https://github.com/nicrie/xeofs/commit/0f9c32e48dd6c9069c11802a13a3f0113e5f07f5)) - nicrie -- some minor corrections in docstrings - ([75eed31](https://github.com/nicrie/xeofs/commit/75eed31f2cdf33a896174aca77c33ec4bc3791eb)) - nicrie -- add example and update docs - ([8bed38a](https://github.com/nicrie/xeofs/commit/8bed38a79094ece72487b619aa01cd45fa276a80)) - nicrie -- update ROCK PCA to documentation - ([3a7394d](https://github.com/nicrie/xeofs/commit/3a7394d57fb4e9d79dfffef5b32df5af1a52e179)) - nicrie -- adding example for ROCK PCA - ([8c6da93](https://github.com/nicrie/xeofs/commit/8c6da93f7c6e99780299e2687960c6a22e7c6661)) - nicrie -- update README - ([9e3210d](https://github.com/nicrie/xeofs/commit/9e3210d190da254850ea17c70011dab916bda24c)) - nicrie -- change examples - ([1c69645](https://github.com/nicrie/xeofs/commit/1c6964542dcfe3d794c6a01442822f57d422a681)) - nicrie -- update README - ([2d28995](https://github.com/nicrie/xeofs/commit/2d28995a9e6c5ce1424721497eef6e97a6430e45)) - nicrie -- fix some minor errors - ([d5d3f73](https://github.com/nicrie/xeofs/commit/d5d3f73b27814b947903a30cf6cbde8aaf5dc67b)) - nicrie -- add bibtex - ([1428ebf](https://github.com/nicrie/xeofs/commit/1428ebfc5d65a62044a3f9f9fb20a4636dbfb891)) - nicrie -- bibtex not showing up on Github ;) - ([0c2a663](https://github.com/nicrie/xeofs/commit/0c2a6635ee5942f2c38f28a1f529ec6a4a5e24bd)) - nicrie - -### 🛠️ Refactoring - -- Merge branch 'main' of github.com:nicrie/xeofs into develop - ([73e2473](https://github.com/nicrie/xeofs/commit/73e247383bd9340d717ce4e8d09637f97d963d03)) - nicrie - -### ✅ Tests - -- add simple test cases for MCA Rotator - ([900d76e](https://github.com/nicrie/xeofs/commit/900d76ec1298b3c604c8734a41d8335a9446249e)) - nicrie - -### ⚙️ Miscellaneous - -- add numba as dependency - ([f627d92](https://github.com/nicrie/xeofs/commit/f627d9270ca2c850c619ba428eaeeb315740f83a)) - nicrie -- remove numba dependency - ([0979fcf](https://github.com/nicrie/xeofs/commit/0979fcf4111fc82c055269b4d69bd5a237bd21c0)) - nicrie - ---- -## [0.6.0](https://github.com/nicrie/xeofs/compare/v0.5.0..v0.6.0) - 2022-08-22 - -### ⭐ Features - -- bootstrapper base class - ([f4ee31a](https://github.com/nicrie/xeofs/commit/f4ee31a9fe83637c1a641f6d1d05844ed15c0ba7)) - nicrie -- bootstrapper for numpy class - ([c5923b3](https://github.com/nicrie/xeofs/commit/c5923b3822178f9ad63837ea841dbe408e8cb3f0)) - nicrie -- bootstrapper for pandas - ([a32b1d3](https://github.com/nicrie/xeofs/commit/a32b1d30a33d695b4c49a121fc343d57a68ec3d4)) - nicrie -- bootstrapper for xarray - ([f807ea6](https://github.com/nicrie/xeofs/commit/f807ea6dd374e989bab0a95f1ac3e5fb0a9dc282)) - nicrie -- add bootstrap methods - ([d5f6797](https://github.com/nicrie/xeofs/commit/d5f6797ab087baabcdf71af325b0754bb3495477)) - nicrie -- add MCA base class - ([58612e4](https://github.com/nicrie/xeofs/commit/58612e40ad225ce4ca30757904e5f7836b3202bc)) - nicrie -- add MCA support for numpy - ([8ded4df](https://github.com/nicrie/xeofs/commit/8ded4df531281b3e19359a5d26f3e5bf4c2db320)) - nicrie -- add MCA support for pandas - ([834d7dd](https://github.com/nicrie/xeofs/commit/834d7dda131ffaf4336f775519f34228ddf62d69)) - nicrie -- add MCA support for xarray - ([e816e36](https://github.com/nicrie/xeofs/commit/e816e3699928d19e828fe0bb41b5003bba6a264e)) - nicrie -- add MCA - ([34a82d1](https://github.com/nicrie/xeofs/commit/34a82d103699cb1b1607e2418eb3c0889fad96fb)) - nicrie - -### 🐛 Bug Fixes - -- set informative names of Dataframes and DataArrays - ([b5b5286](https://github.com/nicrie/xeofs/commit/b5b528678becdf80b511a3883485304341c09692)) - nicrie - -### 🔖 Documentation - -- add zenodo badge - ([4f338ef](https://github.com/nicrie/xeofs/commit/4f338ef473ac1e742452f130fef7604d0c33dc5f)) - nicrie -- add install instructions for conda - ([ef293e5](https://github.com/nicrie/xeofs/commit/ef293e5a97b294c0aeea070a9b77fa33f214dcdf)) - nicrie -- add simple example for bootstrapping - ([ba62057](https://github.com/nicrie/xeofs/commit/ba620578b379636a0fff7e914bf753c1c5397f73)) - nicrie -- add docstrings to bootstrapping methods - ([9c8145c](https://github.com/nicrie/xeofs/commit/9c8145ccd26d1a5150f6c33bb157501cf6d42bca)) - nicrie -- add figure to bootstrapping example - ([69894a0](https://github.com/nicrie/xeofs/commit/69894a0363eda7886969ff7544ed069067bf1f51)) - nicrie -- reorganize examples - ([68d9db0](https://github.com/nicrie/xeofs/commit/68d9db004ff23574fafb7b69cc85c7b2b33812c0)) - nicrie -- add MCA example - ([4fb881e](https://github.com/nicrie/xeofs/commit/4fb881edcad9e7171d8045935ef32fa6a87caff0)) - nicrie -- update documentation and docstrings - ([b8fffdc](https://github.com/nicrie/xeofs/commit/b8fffdc32387ed1ceea63674675d2ac437fe85d9)) - nicrie -- minor changes in text and example arrangements - ([b7f1628](https://github.com/nicrie/xeofs/commit/b7f162800f012e816a6243cfe3e321cf7d9d3aeb)) - nicrie -- remove some old examples - ([625dd08](https://github.com/nicrie/xeofs/commit/625dd0827cd3bda178b3c83629d399947c1b5877)) - nicrie -- minor restructuring - ([dbdc885](https://github.com/nicrie/xeofs/commit/dbdc8850befe142d567181250793202dc0e68c44)) - nicrie - -### 🎨 Style - -- remove some comments - ([4eae075](https://github.com/nicrie/xeofs/commit/4eae075f13baa10677b84db608071ee859858085)) - nicrie -- refer to issue why nodefaults is necessary - ([2aed6f3](https://github.com/nicrie/xeofs/commit/2aed6f35e67881f8ecda291d927f7991f89aea3b)) - nicrie -- correct an indent that prevented xeofs being published to pypi - ([1ee531c](https://github.com/nicrie/xeofs/commit/1ee531c83f49125c8d8ce1f40a166a4fe4f5e78b)) - nicrie - -### ✅ Tests - -- simple tests for Bootstrapper to compile without error - ([ca3141a](https://github.com/nicrie/xeofs/commit/ca3141a4eff602eb808c0ec30530bf2cddb1d14d)) - nicrie -- add MCA test wrapper - ([eb2a280](https://github.com/nicrie/xeofs/commit/eb2a2808259454adfaba0ce35dff75a445ccab29)) - nicrie - -### ⚙️ Miscellaneous - -- Merge branch 'main' of github.com:nicrie/xeofs into main - ([00eb862](https://github.com/nicrie/xeofs/commit/00eb8629e92bf3a9ce99201966cfdeb04fbbf57d)) - nicrie -- Merge branch 'main' into develop - ([14839d4](https://github.com/nicrie/xeofs/commit/14839d4c0de8ef55cbebdec93387765b69d079fd)) - nicrie -- add tqdm as dependency - ([0de85e5](https://github.com/nicrie/xeofs/commit/0de85e5aa733d81836543fb02dba34387cf6ee18)) - nicrie -- repair missing tqdm dependency - ([46fd5f2](https://github.com/nicrie/xeofs/commit/46fd5f2f40d091bada75f1e48181fd4230b1e1d5)) - nicrie -- remove defaults from anaconda channels when building readthedocs - ([0dbcf81](https://github.com/nicrie/xeofs/commit/0dbcf81a95a357b4a2c83a531d056c9a0cb19245)) - nicrie -- docs do not compile properly, try changing version in conf.py - ([0a73edb](https://github.com/nicrie/xeofs/commit/0a73edbc30578b9598f18018180af567758ac7a9)) - nicrie -- change conf - ([a73ef85](https://github.com/nicrie/xeofs/commit/a73ef85739bbf38f915add70319032c4c2d3791d)) - nicrie - ---- -## [0.5.0](https://github.com/nicrie/xeofs/compare/v0.4.0..v0.5.0) - 2022-03-12 - -### ⭐ Features - -- Merge branch 'main' into develop - ([6d2d646](https://github.com/nicrie/xeofs/commit/6d2d6469768d3b91c63358d561b63f9581ebf2a8)) - nicrie -- add base and xarray class for multivariate EOF analysis - ([5ba07f0](https://github.com/nicrie/xeofs/commit/5ba07f0e7c211e9b1a19a44d66d85d3ffc30a4d3)) - nicrie -- add support for multivariate EOF analysis - ([fa9503a](https://github.com/nicrie/xeofs/commit/fa9503a2a789404471b2d85121d54e575a83128c)) - nicrie -- add support for multivariate EOF analysis - ([53961d9](https://github.com/nicrie/xeofs/commit/53961d974cda8bc6b24466c496058efc4d676a4b)) - nicrie - -### 🔖 Documentation - -- add zenodo badge - ([7792953](https://github.com/nicrie/xeofs/commit/7792953e478eeb0e772563999a6ee0688d06ad76)) - nicrie -- add example for multivariate EOF analysis - ([7ae2ae8](https://github.com/nicrie/xeofs/commit/7ae2ae8180a0f997c2f31d45eba5daa747c9900d)) - nicrie -- add example - ([07b3bb8](https://github.com/nicrie/xeofs/commit/07b3bb8d72f2f850dfa61e08613954b7c11cc99a)) - nicrie -- add example for multivariate EOF analysis - ([59a1f1b](https://github.com/nicrie/xeofs/commit/59a1f1be37bb1bed5d9288841ddde891c03c7600)) - nicrie -- update README - ([fdc76ee](https://github.com/nicrie/xeofs/commit/fdc76ee567d442cc310571b808a6947774f23e06)) - nicrie - -### 🛠️ Refactoring - -- remove class MultivariateEOF - ([4fa1ff8](https://github.com/nicrie/xeofs/commit/4fa1ff873557ad63659507a94a0e0f6f3ff5a1f7)) - nicrie -- consistent transformer and multi_transformer definition - ([9445769](https://github.com/nicrie/xeofs/commit/9445769253c2014aa85ba45b73cb08f13664f4b4)) - nicrie -- tests for new transformer definition - ([cf330ff](https://github.com/nicrie/xeofs/commit/cf330ffb0dca2184398a1bb6bd49a3cc456af586)) - nicrie - -### ✅ Tests - -- multivariate EOF analysis - ([b0d0b33](https://github.com/nicrie/xeofs/commit/b0d0b33ea75b1359d110b531aa7da0d884a06acd)) - nicrie - -### ⚙️ Miscellaneous - -- loose numpy version constrain - ([375d675](https://github.com/nicrie/xeofs/commit/375d6759e388eee1597eacdc7e472b6433422db6)) - nicrie - ---- -## [0.4.0](https://github.com/nicrie/xeofs/compare/v0.3.0..v0.4.0) - 2022-03-02 - -### ⭐ Features - -- eofs as correlation for EOF analysis - ([e53d449](https://github.com/nicrie/xeofs/commit/e53d4494c96b6335911a79c325382ddc0a57fae4)) - nicrie -- eofs as correlation for rotated EOF analysis - ([cb8c472](https://github.com/nicrie/xeofs/commit/cb8c472f12906d8b2d2750847b1ae62a741fb4f8)) - nicrie -- add eofs as correlation - ([85960ab](https://github.com/nicrie/xeofs/commit/85960abf96283978748e283053175577211ade74)) - nicrie -- add scaling for PCs and EOFs - ([c2c6fe1](https://github.com/nicrie/xeofs/commit/c2c6fe190b7a481f3c9193b1ce541c57e3a80e94)) - nicrie -- allow different scalings of EOFs an PCs - ([ea39f02](https://github.com/nicrie/xeofs/commit/ea39f023e1c0cf980063caf2bc2fa7daaac7c8ab)) - nicrie -- reconstruct input data for EOF analysis - ([7ed306a](https://github.com/nicrie/xeofs/commit/7ed306add5bd7cc9ef9b2e14d486fd7887c1d388)) - nicrie -- reconstruct input data after rotation - ([0c9479e](https://github.com/nicrie/xeofs/commit/0c9479e59a4a016f442b532889437e38c4a0e9bf)) - nicrie -- allow to reconstruct original data with arbitrary mode combination - ([be095d7](https://github.com/nicrie/xeofs/commit/be095d77d5d452853a36a6719c7de8edf17bed5b)) - nicrie -- project unseen data onto EOFs - ([64e38b1](https://github.com/nicrie/xeofs/commit/64e38b120a5c7e16431551e4c80f9b4a2a515eb4)) - nicrie -- project unseen data onto EOFs - ([341546b](https://github.com/nicrie/xeofs/commit/341546b8b74cb1f91105aefd409fab8a087cca9a)) - nicrie -- project unseen data onto rotated EOFs - ([63b2d3a](https://github.com/nicrie/xeofs/commit/63b2d3afdcb9b170b3fdbe5d38a6386463423e4a)) - nicrie -- project new data onto EOFs and rotated EOFs - ([d8b0e57](https://github.com/nicrie/xeofs/commit/d8b0e57622bc6dec1b45ac94821eaf369a335704)) - nicrie - -### 🐛 Bug Fixes - -- back_transform automatically add feature coords - ([0fef30d](https://github.com/nicrie/xeofs/commit/0fef30da1bfea0d5b26070474fbe2ee826997dd4)) - nicrie -- PC projections was missing -1 correction for degrees of freedom - ([a243a26](https://github.com/nicrie/xeofs/commit/a243a26cce09d29b318cb28011e815916f25c2e4)) - nicrie -- fix incorrect dof for rotated PC scaling - ([addeb82](https://github.com/nicrie/xeofs/commit/addeb82b0c68f5ffbd6c3f9559503cf88c1ba525)) - nicrie - -### 🔖 Documentation - -- update examples - ([5795ffa](https://github.com/nicrie/xeofs/commit/5795ffa0e6902abb536c8912f7b55874b9a141b6)) - nicrie -- add matplotlib to environment to generate example - ([2346fcb](https://github.com/nicrie/xeofs/commit/2346fcb0b2f8b4b4c62d3bd87891ed107914634c)) - nicrie -- update links to examples - ([44a4353](https://github.com/nicrie/xeofs/commit/44a4353c648080aedaa62701d1efba7f757b3e32)) - nicrie -- install current master branch of sphinx-gallery - ([8426033](https://github.com/nicrie/xeofs/commit/8426033b89b01bac1154532d82967f07c694db42)) - nicrie -- forgot to specifiy master branch - ([2c827ba](https://github.com/nicrie/xeofs/commit/2c827ba0e73526cd711f280911025807d2e40837)) - nicrie -- too many "install" ;) - ([ea66ba6](https://github.com/nicrie/xeofs/commit/ea66ba65be9a33fa99d6b648cec5fc69cde64b85)) - nicrie -- remove older version of sphinx-gallery - ([938f294](https://github.com/nicrie/xeofs/commit/938f2947a91074ebafb4d031403d5c7b2ee3e539)) - nicrie -- update README - ([29f1b4d](https://github.com/nicrie/xeofs/commit/29f1b4d7c592038d9402ba68fe61cd94b9f72045)) - nicrie -- add eofs as correlations - ([64c60c1](https://github.com/nicrie/xeofs/commit/64c60c136ba39805ac9c4886f2f635efdc1e7eb4)) - nicrie -- update docs - ([28e248b](https://github.com/nicrie/xeofs/commit/28e248b26b840e487370bf7d33ab73fb6b445ce4)) - nicrie -- add project_onto_eofs to autosummary - ([af7d1f2](https://github.com/nicrie/xeofs/commit/af7d1f29a33e0e782c9f1cc58932f95f729ee1a6)) - nicrie -- update README - ([58f539b](https://github.com/nicrie/xeofs/commit/58f539b2d353875d3a3d6da7707f4a1b69079755)) - nicrie -- update README - ([8c8cb29](https://github.com/nicrie/xeofs/commit/8c8cb29a52496302fa2893f74aa05a9d855fb005)) - nicrie -- update README - ([2d00a71](https://github.com/nicrie/xeofs/commit/2d00a7126f5248dd766815071857e5c1af63bd28)) - nicrie -- update README - ([c52763b](https://github.com/nicrie/xeofs/commit/c52763bbdb4de3f261d996db47125cf44edb6113)) - nicrie -- update README - ([982d7e3](https://github.com/nicrie/xeofs/commit/982d7e3520937b4b696beaa5a4753267a2278280)) - nicrie -- fix typo - ([d5505c6](https://github.com/nicrie/xeofs/commit/d5505c6c6e9a010cb836b609ebbf7dac6b38f67e)) - nicrie -- update README - ([5693fe9](https://github.com/nicrie/xeofs/commit/5693fe9f2e2b10f1d0c364d0aba1eb47c84e9bc9)) - nicrie - -### 🛠️ Refactoring - -- helper fuction for mode selection - ([318e8ea](https://github.com/nicrie/xeofs/commit/318e8ea68a559574721bb6c2eecf6b3bbfdcf7ab)) - nicrie -- dataframe and dataarray transformer return index and coords - ([1c6252a](https://github.com/nicrie/xeofs/commit/1c6252aa0b9556972d9268d2a9232c3f006e9c7b)) - nicrie -- consistent class names - ([5d59fad](https://github.com/nicrie/xeofs/commit/5d59fade36e76b740a09591b3223dcd82e24392d)) - nicrie -- consistent class names in tests - ([40416fa](https://github.com/nicrie/xeofs/commit/40416fa8adde618932128721f81d0368e544da60)) - nicrie - -### 🎨 Style - -- remove comments - ([835951b](https://github.com/nicrie/xeofs/commit/835951b8033c7e4960779f4356b8b81a94d83f8b)) - nicrie - -### ✅ Tests - -- eof as correlation - ([d84454c](https://github.com/nicrie/xeofs/commit/d84454c6ab9ef547b80b8911b6278780b141bd49)) - nicrie -- add scaling to wrapper tests - ([7cad9fe](https://github.com/nicrie/xeofs/commit/7cad9fe6816e42384676e4259edf4e93f743dd85)) - nicrie -- add tests for reconstruct_X - ([1029f0c](https://github.com/nicrie/xeofs/commit/1029f0c9a59cfc17b8ecc3d486eb9de349d8626c)) - nicrie -- projection onto EOFs - ([49dc6d8](https://github.com/nicrie/xeofs/commit/49dc6d8e7a885ee22c881b4726c931615485920d)) - nicrie - ---- -## [0.3.0](https://github.com/nicrie/xeofs/compare/v0.2.0..v0.3.0) - 2022-02-20 - -### ⭐ Features - -- add weight transformer - ([52b98e6](https://github.com/nicrie/xeofs/commit/52b98e6189d144bba4320ceb0dd2c43c1548e8c9)) - nicrie -- add weight support to EOF classes - ([8821108](https://github.com/nicrie/xeofs/commit/882110879a31af5b632efb5a39bf6d6afebe2fb7)) - nicrie -- Add support for weighted EOF analysis including coslat weighting - ([654b437](https://github.com/nicrie/xeofs/commit/654b437f64bf5c6dc9be811e891de2c5d1a3d2d9)) - nicrie -- add Rotator base class - ([d024d81](https://github.com/nicrie/xeofs/commit/d024d8151429d4bfd6a374207168421ac02242c2)) - nicrie -- add varimax and promax algorithms - ([f1e928f](https://github.com/nicrie/xeofs/commit/f1e928fcb20f2ccfa2f450d2ba45230d01ba1e4c)) - nicrie -- add Rotator interface for numpy, pandas, xarray - ([050b883](https://github.com/nicrie/xeofs/commit/050b883113166811bd5f8e6dc35cfcb162fa7503)) - nicrie -- add Varimax and Promax rotation - ([b42ba16](https://github.com/nicrie/xeofs/commit/b42ba160f183d7a22a8555b19bf7de340663742b)) - nicrie - -### 🐛 Bug Fixes - -- add error messages when calling invalid coslat weighting - ([6104e69](https://github.com/nicrie/xeofs/commit/6104e69b297f42c7aef68e20ca753394fc9a50c8)) - nicrie -- coslat error was too restrictive - ([faece55](https://github.com/nicrie/xeofs/commit/faece55ccdfaa91f73b6dcce74959dead9736388)) - nicrie -- always center data X - ([4a58dfc](https://github.com/nicrie/xeofs/commit/4a58dfc0cc400aa3b20ae0d2c904969d0e19109b)) - nicrie -- incorrect number of mode index for DataArray caller - ([4e610ac](https://github.com/nicrie/xeofs/commit/4e610aca9b2db726c6351f2615adbb482d011722)) - nicrie - -### 🔖 Documentation - -- some minor changes in examples - ([9611eea](https://github.com/nicrie/xeofs/commit/9611eeac466078ac4e008373005e7cd0c98607bd)) - nicrie -- add example for weigted EOF analysis - ([9dedab2](https://github.com/nicrie/xeofs/commit/9dedab2a25a0f18595e618ca986abe0b57b5a23f)) - nicrie -- add example for rotated EOF analysis - ([efc364a](https://github.com/nicrie/xeofs/commit/efc364a925b33a167bfdfdbb71fd73ebd7b6c6f7)) - nicrie - -### 🎨 Style - -- rename mode_idx to idx_mode - ([d0508b1](https://github.com/nicrie/xeofs/commit/d0508b1f9be2be897f644aaa69807c9adeed0ea1)) - nicrie -- rename mode_idx to idx_mode - ([ac89e3b](https://github.com/nicrie/xeofs/commit/ac89e3bc07c0d0a64bd50442e54c15f95715629c)) - nicrie - -### ✅ Tests - -- add array fixtures with arbitrary shapes - ([7021ef6](https://github.com/nicrie/xeofs/commit/7021ef665faedd3540bc02a478ac0d37b0db1876)) - nicrie -- add tests for weights - ([318b225](https://github.com/nicrie/xeofs/commit/318b225a43939b2f23cf38f937c68e6fb1c91bb5)) - nicrie -- add simple test for coslat weighting - ([793148b](https://github.com/nicrie/xeofs/commit/793148ba0af84dbe21add0d0c25dfebe5e402889)) - nicrie -- verify against normalized PCs - ([48f054f](https://github.com/nicrie/xeofs/commit/48f054fbf34bf25b71de965f2390fbfae7d247f1)) - nicrie -- add test for Rotator classes - ([bb783e7](https://github.com/nicrie/xeofs/commit/bb783e727b6bfed92eba38ca54a314d90963f838)) - nicrie - ---- -## [0.2.0](https://github.com/nicrie/xeofs/compare/v0.1.2..v0.2.0) - 2022-02-17 - -### ⭐ Features - -- add support for multidimensional axes - ([7c31c58](https://github.com/nicrie/xeofs/commit/7c31c58f60376bac57fe42bef58ad9e46942fcb7)) - nicrie - -### 🐛 Bug Fixes - -- allow multidimensional axis for decomposition - ([e09a420](https://github.com/nicrie/xeofs/commit/e09a420561c41c83483ecd1a718d0d6c86ed8c78)) - nicrie - -### 🔖 Documentation - -- place badges on same line - ([e2d4dc3](https://github.com/nicrie/xeofs/commit/e2d4dc380accca197a76c16f815b35f889140150)) - nicrie -- repair docs due to importlib being installed twice - ([0e21ebd](https://github.com/nicrie/xeofs/commit/0e21ebd0551ba7813ab5219febfda79dd26aec1a)) - nicrie -- remove conflicting package versions - ([49636ae](https://github.com/nicrie/xeofs/commit/49636ae4f456ace63ed19bf081ce2fdf35dbbc42)) - nicrie -- add installation instructions - ([43e2563](https://github.com/nicrie/xeofs/commit/43e2563e986f3217bce6e9fcd643ea0df0297cc4)) - nicrie -- update docs - ([7b19b5b](https://github.com/nicrie/xeofs/commit/7b19b5bc35564317f49311c1a3705ce0893291dc)) - nicrie -- update docstrings - ([e02b6ec](https://github.com/nicrie/xeofs/commit/e02b6ec4545bc9b13b48f27a00b4da77e1358037)) - nicrie -- add download badge - ([9a96fd1](https://github.com/nicrie/xeofs/commit/9a96fd1e8d589b4c80b4498224f1851ec0428565)) - nicrie -- add EOF s-mode and t-mode gallery example - ([5f371b7](https://github.com/nicrie/xeofs/commit/5f371b7ee52b64315a8c7940bb993605823e4455)) - nicrie -- try to solve readthedoc version number - ([981bcdd](https://github.com/nicrie/xeofs/commit/981bcdd4865219574bf154bbd6c237c23ee48563)) - nicrie -- try to solve the readthedocs issue with importlib - ([b4cdd9e](https://github.com/nicrie/xeofs/commit/b4cdd9ec4ca4d75df9e8a3ba7910163c42970cbe)) - nicrie -- solve readthedoc version issue by installing xeofs first - ([7afdd78](https://github.com/nicrie/xeofs/commit/7afdd78af786ca5048c748ea09985aecc0d9b7b0)) - nicrie - -### 🎨 Style - -- update typings of EOF techniques - ([668a375](https://github.com/nicrie/xeofs/commit/668a375ffe3e16ce557e52d730e39ce24c393bea)) - nicrie -- correct invalid typing - ([3745308](https://github.com/nicrie/xeofs/commit/374530886fde4a30fe1ec41f1fac692f44a4b4ce)) - nicrie -- change error message - ([ad13409](https://github.com/nicrie/xeofs/commit/ad134098f60a77fcb781336259301d82cfa0b097)) - nicrie - -### ✅ Tests - -- update tests for _array_transformer - ([47fe5d2](https://github.com/nicrie/xeofs/commit/47fe5d2134431b132875395564a080b2a5fcc36d)) - nicrie - -### ⚙️ Miscellaneous - -- Merge branch 'main' into develop - ([e1c3534](https://github.com/nicrie/xeofs/commit/e1c3534940473a266be52686ae01307a0109e548)) - nicrie - ---- -## [0.1.2](https://github.com/nicrie/xeofs/compare/v0.1.1..v0.1.2) - 2022-02-15 - -### 🐛 Bug Fixes - -- pandas and xarray eofs back_transform was called twice - ([4fa2bfb](https://github.com/nicrie/xeofs/commit/4fa2bfb3f3a669ad1fd2b8a72f2fb6a64eab927a)) - nicrie - -### 🔖 Documentation - -- add batches and link to documentation - ([a7dd2d0](https://github.com/nicrie/xeofs/commit/a7dd2d0d6cdde42c6c9e9367bfd55d2aa077ba4d)) - nicrie -- add installation instruction - ([9512d34](https://github.com/nicrie/xeofs/commit/9512d3450651384f48582458d2896c4d1ba355cc)) - nicrie - -### ✅ Tests - -- add EOF numpy interface - ([b5ff975](https://github.com/nicrie/xeofs/commit/b5ff975621a271bf1c78d04fcc5607cb5ef46421)) - nicrie -- add more test cases for numpy wrapper - ([d9ca6ae](https://github.com/nicrie/xeofs/commit/d9ca6ae97c3421bc352fe89cbd22b44bc6e9d548)) - nicrie -- add tests for pandas wrapper - ([7a8786d](https://github.com/nicrie/xeofs/commit/7a8786da3b0a854766481516cacd4bee7301ca92)) - nicrie -- add tests for xarray wrapper - ([d5f20cb](https://github.com/nicrie/xeofs/commit/d5f20cbb76299ead56ad82d27d8b53223a156773)) - nicrie - -### ⚙️ Miscellaneous - -- Merge branch 'main' into develop - ([f51e4d2](https://github.com/nicrie/xeofs/commit/f51e4d2fa90ae41d4c390bffa4b1a09c32efd914)) - nicrie -- add netcdf4 as dev-dependency - ([4c4897c](https://github.com/nicrie/xeofs/commit/4c4897ce65c39132989a3daede2c144d805d5dc5)) - nicrie - ---- -## [0.1.1] - 2022-02-15 - -### 🐛 Bug Fixes - -- add __version__ - ([739ae74](https://github.com/nicrie/xeofs/commit/739ae740e8a8f740bd69d73a28daebec7117bcb1)) - nicrie -- add flake8 dependency - ([483cf42](https://github.com/nicrie/xeofs/commit/483cf4294e5fda29da1477bee073ba552bb40de9)) - nicrie -- add development dependencies - ([e1cc1f6](https://github.com/nicrie/xeofs/commit/e1cc1f669fd218aadf1665b54f441ed1265c6395)) - nicrie -- wrong pytest version - ([774b2d6](https://github.com/nicrie/xeofs/commit/774b2d64af46cc6731e270a25c3e4c524c3d0d94)) - nicrie -- allow standardized EOF analysis - ([6e80f78](https://github.com/nicrie/xeofs/commit/6e80f7867a35079b64a447604701f9e689e63f5f)) - nicrie -- typo in CI - ([b34ccc5](https://github.com/nicrie/xeofs/commit/b34ccc511a412dd5920ec6a30d764794ca52aad9)) - nicrie - -### 🔖 Documentation - -- update dependencies - ([05ceb68](https://github.com/nicrie/xeofs/commit/05ceb68bc77586663d9ddcf36c3e6c42d3947c72)) - nicrie - -### ⚙️ Miscellaneous - -- migrate to poetry - ([f7def8a](https://github.com/nicrie/xeofs/commit/f7def8a8b2e6ca5830c2edd6a81d612b15563189)) - nicrie -- migrate to poetry - ([a5db259](https://github.com/nicrie/xeofs/commit/a5db25927ceac66ddab1f0324b777316c09d22a2)) - nicrie -- use pre-commit hooks to ensure conventional commits - ([1c04785](https://github.com/nicrie/xeofs/commit/1c0478579c9d225a11f32ae03b4233a48f7633ed)) - nicrie -- fix incorrect call of coverage - ([d9ee80e](https://github.com/nicrie/xeofs/commit/d9ee80e9984411252fe9570f8b2ea6cc86844afc)) - nicrie -- semantic-release is triggered for PR on main - ([e9077a2](https://github.com/nicrie/xeofs/commit/e9077a2369f5ceb8c64e7909fcb648dfcb5a4191)) - nicrie -- migrate python-semantic-release to github workflows - ([c27de9a](https://github.com/nicrie/xeofs/commit/c27de9acc08fab78d26eae4aced65eb1078a282b)) - nicrie -- Merge branch 'main' into develop - ([9cdef9b](https://github.com/nicrie/xeofs/commit/9cdef9b2eac6a3a997d86506a8ea1ad48395cef7)) - nicrie -- upload package to pypi - ([989a278](https://github.com/nicrie/xeofs/commit/989a2784694e5880646e6d16362eb291a17d1fec)) - nicrie - - From 70025c9791d9fb9d647889955ab03e93e166fe94 Mon Sep 17 00:00:00 2001 From: Niclas Rieger <45175997+nicrie@users.noreply.github.com> Date: Sun, 15 Sep 2024 22:07:43 +0200 Subject: [PATCH 7/7] docs(changelog): update commit parsers in cliff.toml (#233) --- cliff.toml | 10 +++++-- docs/content/whats_new/CHANGELOG.md | 43 ++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/cliff.toml b/cliff.toml index 95a9fad..a0ba0dd 100644 --- a/cliff.toml +++ b/cliff.toml @@ -62,6 +62,12 @@ commit_preprocessors = [ ] # regex for parsing and grouping commits commit_parsers = [ + { message = "^chore: release", skip = true }, + { message = "^chore\\(release\\):", skip = true }, + { message = "^chore: merge (branch )?'[^']+' into '[^']+'", skip = true }, + { message = "^chore: merge (branch )?[^ ]+ into [^ ]+", skip = true }, + { message = "^docs(changelog): update changelog", skip = true }, + { message = "^docs\\(changelog\\): update changelog", skip = true }, { message = "^feat", group = "⭐ Features" }, { message = "^perf", group = "🚀 Performance" }, { message = "^fix", group = "🐛 Bug Fixes" }, @@ -70,10 +76,8 @@ commit_parsers = [ { message = "^style", group = "🎨 Style" }, { message = "^revert", group = "♻️ Revert" }, { message = "^test", group = "✅ Tests" }, - { message = "^chore: release", skip = true }, - { message = "^chore\\(release\\):", skip = true }, - { message = "^chore|ci|build", group = "⚙️ Miscellaneous" }, { body = ".*security", group = "🔒 Security" }, + { message = "^chore|ci|build", group = "⚙️ Miscellaneous" }, ] # protect breaking changes from being skipped due to matching a skipping commit_parser protect_breaking_commits = false diff --git a/docs/content/whats_new/CHANGELOG.md b/docs/content/whats_new/CHANGELOG.md index e22c35e..2c132cb 100644 --- a/docs/content/whats_new/CHANGELOG.md +++ b/docs/content/whats_new/CHANGELOG.md @@ -2,6 +2,36 @@ All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines. +--- +## [unreleased] + +### 🚀 Performance + +- avoid redundant data when saving Rotator models (#229) - ([e2525bb](https://github.com/nicrie/xeofs/commit/e2525bb0f602337589d357860e0f8aeb01a8f58b)) - Niclas Rieger +- speed up rotation by about 20% (#228) - ([bcbaa9e](https://github.com/nicrie/xeofs/commit/bcbaa9effd0515f8d8ee1367e0f94a8cd5c771f4)) - Niclas Rieger + +### 🐛 Bug Fixes + +- **(cross)** correct saving and loading of CPCCA and Rotator models (#225) - ([7cba749](https://github.com/nicrie/xeofs/commit/7cba749418d0313c7404f2a9991675c058f159dc)) - Niclas Rieger +- xarray 2024.09.0 compatility (#227) - ([5ed9753](https://github.com/nicrie/xeofs/commit/5ed9753bc7d8ce349ac75f9043e61c2f6a20677e)) - Sam Levang +- correct whitening in HilbertCPCCA models (#230) - ([3ba531c](https://github.com/nicrie/xeofs/commit/3ba531c62a753feecec15d24ceabba317c7256e2)) - Niclas Rieger + +### 🔖 Documentation + +- fix changelog path (#232) - ([dff493b](https://github.com/nicrie/xeofs/commit/dff493bc50f701af80b3ce0ce34f220ab7ae7469)) - Niclas Rieger + +--- +## [3.0.1](https://github.com/nicrie/xeofs/compare/v3.0.0..v3.0.1) - 2024-09-10 + +### 🐛 Bug Fixes + +- **(cross)** fix data augmentation in Hilbert cross-set models (#222) - ([960f744](https://github.com/nicrie/xeofs/commit/960f74429a01d614bd9289d762550128206330a0)) - Niclas Rieger +- **(cross)** compute total squared covariance in PC space (#221) - ([2dd3e86](https://github.com/nicrie/xeofs/commit/2dd3e86d1b41416346a31c6dffd261834990426b)) - Niclas Rieger + +### 🔖 Documentation + +- add migration guide (#217) - ([dfaab53](https://github.com/nicrie/xeofs/commit/dfaab53734f33042d6762aba4799afa5479585c4)) - Niclas Rieger + --- ## [3.0.0](https://github.com/nicrie/xeofs/compare/v2.4.1..v3.0.0) - 2024-09-04 @@ -22,8 +52,6 @@ All notable changes to this project will be documented in this file. See [conven ### 🔖 Documentation -- **(changelog)** update changelog - ([be6664d](https://github.com/nicrie/xeofs/commit/be6664d08c49e5f588bc44bcd8f1852d4bea2c43)) - github-actions[bot] -- **(changelog)** update changelog - ([a4e1be2](https://github.com/nicrie/xeofs/commit/a4e1be220ed572e6bd8490a0e1a7f4e1ad6ac34d)) - github-actions[bot] - **(readme)** fix broken badges (#204) - ([72e8c60](https://github.com/nicrie/xeofs/commit/72e8c60941bd00a091e5d6d27635622a26aa9bcf)) - Niclas Rieger ### 🛠️ Refactoring @@ -31,10 +59,6 @@ All notable changes to this project will be documented in this file. See [conven - **(typing)** use built-in types for annotations (#208) - ([7505350](https://github.com/nicrie/xeofs/commit/75053507a2a4128d25e2c46fe24ef0f85a07364a)) - Niclas Rieger - [**BREAKING**] reorganize methods into new namespaces (#210) - ([675983f](https://github.com/nicrie/xeofs/commit/675983fbb030380e6bde5ce7ede02346d8c9debe)) - Niclas Rieger -### ⚙️ Miscellaneous - -- merge branch 'main' into develop - ([8acc4ae](https://github.com/nicrie/xeofs/commit/8acc4ae213ce2d91a3ce78fb07dabfff180e0f9f)) - Niclas Rieger - --- ## [2.4.1](https://github.com/nicrie/xeofs/compare/v2.4.0..v2.4.1) - 2024-08-31 @@ -45,8 +69,6 @@ All notable changes to this project will be documented in this file. See [conven ### 🔖 Documentation - **(changelog)** move log to documentation (#199) - ([c9de931](https://github.com/nicrie/xeofs/commit/c9de931327faae972747e3b0d3fbfded4c3b9a91)) - Niclas Rieger -- **(changelog)** update changelog - ([280bd43](https://github.com/nicrie/xeofs/commit/280bd430383ae5fecc47275f1964945b1bd5479b)) - github-actions[bot] -- **(changelog)** update changelog - ([8eed958](https://github.com/nicrie/xeofs/commit/8eed958bbc23dc2d3a5ae8195f02e1b8efb813e5)) - github-actions[bot] - **(decomposer)** improve error messages (#194) - ([9d34060](https://github.com/nicrie/xeofs/commit/9d34060617a45c60b02bf504e38d7b202285ccfc)) - Niclas Rieger - reorganize documentation (#171) - ([aa28770](https://github.com/nicrie/xeofs/commit/aa287706f590c7afe7eeb6d51c30d64b4d285cc0)) - Niclas Rieger - update color design (#181) - ([25598a1](https://github.com/nicrie/xeofs/commit/25598a1fc23e41560e9d85327f7c52c0420825f4)) - Niclas Rieger @@ -74,7 +96,6 @@ All notable changes to this project will be documented in this file. See [conven - **(changelog)** remove old Github Action - ([45b513e](https://github.com/nicrie/xeofs/commit/45b513e57e3f32aa8498b94f5b5e4c84ac61ecfe)) - Niclas Rieger - update Github Issue templates (#172) - ([998774f](https://github.com/nicrie/xeofs/commit/998774fe50259ec2341aa1b21da51a9f077df22c)) - Niclas Rieger -- merge main into develop - ([4e683e6](https://github.com/nicrie/xeofs/commit/4e683e644be0975e3f2e388703a5dd88484da674)) - Niclas Rieger - updating internal code structure (#195) - ([00d5fb7](https://github.com/nicrie/xeofs/commit/00d5fb7dcd176aa6eaf274805ebfc29eb937c8b6)) - Niclas Rieger - update git-cliff Github Action - ([f8b3305](https://github.com/nicrie/xeofs/commit/f8b33053bd2c2bd09808887b890a1d2fab611eb3)) - Niclas Rieger - add deprecation warning for verbose parameter - ([87b23f9](https://github.com/nicrie/xeofs/commit/87b23f9d8dfc219730dda22800c5ec98206dc9d3)) - Niclas Rieger @@ -227,7 +248,6 @@ All notable changes to this project will be documented in this file. See [conven ### ⚙️ Miscellaneous -- merge branch 'main' into develop - ([95a34f6](https://github.com/nicrie/xeofs/commit/95a34f60f9ea3ea6706423b2343de2990d80dbf6)) - Niclas Rieger - add workflow dispatch to Github Action - ([26f8345](https://github.com/nicrie/xeofs/commit/26f834516d1aacecdffc91e425a801c2086bdcd8)) - Niclas Rieger - specify Python Semantic Release version v8.0.0 - ([c7c79e4](https://github.com/nicrie/xeofs/commit/c7c79e448462d96d49cec2ab903363085e5ed9d4)) - Niclas Rieger @@ -269,7 +289,6 @@ All notable changes to this project will be documented in this file. See [conven - merge branches - ([4447b07](https://github.com/nicrie/xeofs/commit/4447b07a948bc4cdd083ef781c605a630e29c6dc)) - Niclas Rieger - bring all-contributors bot back to life - ([8929ed5](https://github.com/nicrie/xeofs/commit/8929ed5a1f6757eb9f55e191eb4d7d417ad37b1b)) - Niclas Rieger - fix config of all-contributor bot - ([1eedf4f](https://github.com/nicrie/xeofs/commit/1eedf4f5c2b69f30ee45be0c110d9cca7e25ce9d)) - Niclas Rieger -- merge branch 'main' into develop - ([f9ef729](https://github.com/nicrie/xeofs/commit/f9ef72920becf750f63bc8ed4f8b80f6c25593e4)) - Niclas Rieger --- ## [2.1.0](https://github.com/nicrie/xeofs/compare/v2.0.3..v2.1.0) - 2023-11-14 @@ -631,9 +650,7 @@ All notable changes to this project will be documented in this file. See [conven - Merge branch 'main' into develop - ([bc4e87d](https://github.com/nicrie/xeofs/commit/bc4e87d469807c4b2bb4679556813e2693affe5f)) - Niclas Rieger - merge branch 'main' of nicrie/xeofs - ([efb5ab9](https://github.com/nicrie/xeofs/commit/efb5ab9c306e9d71a4aab20b6f16ccbfa6a65c56)) - Niclas Rieger -- merge main into develop - ([9f50392](https://github.com/nicrie/xeofs/commit/9f50392d7f99ca6bb7cd13608b630094e0845cb3)) - Niclas Rieger - revert errorenous upgrade to v2 - ([ca22aa4](https://github.com/nicrie/xeofs/commit/ca22aa420c4809deaa114de18164ce8d0f5fb02b)) - Niclas Rieger -- merge branch 'main' into develop - ([e0e8d93](https://github.com/nicrie/xeofs/commit/e0e8d93f2ca33ebb091f76a3e04ba5c18b5276ed)) - Niclas Rieger --- ## [1.0.1](https://github.com/nicrie/xeofs/compare/v1.0.0..v1.0.1) - 2023-07-07